diff --git a/catkit/gen/__init__.py b/catkit/gen/__init__.py index fd630a43..e3006a58 100644 --- a/catkit/gen/__init__.py +++ b/catkit/gen/__init__.py @@ -2,7 +2,7 @@ # (see accompanying license files for details). """Catalysis Generator.""" -from collections import MutableMapping +from collections.abc import MutableMapping import numpy as np import ase diff --git a/catkit/gen/adsorption.py b/catkit/gen/adsorption.py index 3c24af16..a70b295c 100644 --- a/catkit/gen/adsorption.py +++ b/catkit/gen/adsorption.py @@ -55,10 +55,11 @@ def __init__(self, slab, surface_atoms=None, tol=1e-5): self.r1_topology += r1top self.r2_topology += r2top + #self.coordinates = np.array(self.coordinates) self.coordinates = np.array(self.coordinates) self.connectivity = np.array(self.connectivity, dtype=int) - self.r1_topology = np.array(self.r1_topology) - self.r2_topology = np.array(self.r2_topology) + self.r1_topology = np.array(self.r1_topology, dtype=object) + self.r2_topology = np.array(self.r2_topology, dtype=object) self.frac_coords = np.dot(self.coordinates, np.linalg.pinv(slab.cell)) self.slab = slab @@ -91,7 +92,7 @@ def get_coordinates(self, unique=True): def get_topology(self, unique=True): """Return the indices of adjacent surface atoms.""" topology = [self.index[top] for top in self.r1_topology] - topology = np.array(topology) + topology = np.array(topology, dtype=object) if unique: sel = self.get_symmetric_sites() else: @@ -514,7 +515,7 @@ def add_adsorbate( raise ValueError('Specify the index of atom to bond.') elif len(bonds) == 1: - if index is -1: + if index == -1: slab = [] for i, _ in enumerate(self.get_symmetric_sites()): slab += [self._single_adsorption( diff --git a/catkit/gen/route.py b/catkit/gen/route.py index 1f7c5f66..6dc89cb8 100644 --- a/catkit/gen/route.py +++ b/catkit/gen/route.py @@ -207,10 +207,14 @@ def get_response_reactions(epsilon, selection=None, species=False): possible_routes = itertools.combinations(selection, r=s + 1) for sel in possible_routes: values = np.zeros(epsilon.shape[0], dtype=int) - - sigma = np.repeat(epsilon[[sel]][None], s + 1, axis=0) - eye = np.eye(s + 1)[:, :, None] - R = np.concatenate([sigma, eye], axis=2) + #sigma = np.repeat(epsilon[[sel]],axis=0) + sigma = epsilon[list(sel)] # This is a 2D array + # # Add axis 0 and repeat + sigma_expanded = np.repeat(sigma[None, :, :], s + 1, axis=0) # Now a 3D array with shape (s+1, len(sel), epsilon.shape[1]) + # Create the identity part + eye = np.eye(s + 1)[:, :, None] # 3D array with shape (s+1, s+1, 1) + # Now both arrays are 3D and can be concatenated along axis 2 + R = np.concatenate([sigma_expanded, eye], axis=2) # Does not convert to correct integers without round values[[sel]] = np.round(np.linalg.det(R)) diff --git a/catkit/gen/surface.py b/catkit/gen/surface.py index 3619c267..e91fbb15 100644 --- a/catkit/gen/surface.py +++ b/catkit/gen/surface.py @@ -583,7 +583,7 @@ def transform_ab(slab, matrix, tol=1e-5): corners = np.dot(scorners_newcell, newcell[:2, :2]) scorners = np.linalg.solve(slab.cell[:2, :2].T, corners.T).T - rep = np.ceil(scorners.ptp(axis=0)).astype(int) + rep = np.ceil(np.ptp(scorners,axis=0)).astype(int) slab *= (rep[0], rep[1], 1) slab.set_cell(newcell) diff --git a/catkit/gratoms.py b/catkit/gratoms.py index cc7766cd..c20fcbc2 100644 --- a/catkit/gratoms.py +++ b/catkit/gratoms.py @@ -320,7 +320,7 @@ def __imul__(self, m): raise ValueError( 'Cannot repeat along undefined lattice vector') - M = np.product(m) + M = np.prod(m) n = len(self) for name, a in self.arrays.items(): diff --git a/catkit/tests/gen/README b/catkit/tests/gen/README new file mode 100644 index 00000000..c0908c85 --- /dev/null +++ b/catkit/tests/gen/README @@ -0,0 +1 @@ +These tests are adopted from "https://catkit-jboes.readthedocs.io/en/latest/_static/frontmatter/catgen.html#org0e3e58d" diff --git a/catkit/tests/gen/test1.1.2.py b/catkit/tests/gen/test1.1.2.py new file mode 100644 index 00000000..2355c647 --- /dev/null +++ b/catkit/tests/gen/test1.1.2.py @@ -0,0 +1,12 @@ +from catkit.build import surface +from ase.io import write + +# Size is 3 layers and 3 times the unit area of the primitve cell +atoms = surface('Pd', size=(3, 3), miller=(1, 1, 1), vacuum=4) + +print(atoms.connectivity) + +for i in atoms.get_surface_atoms(): + atoms[i].symbol = 'Au' + +atoms.edit() diff --git a/catkit/tests/gen/test1.1.3.py b/catkit/tests/gen/test1.1.3.py new file mode 100644 index 00000000..9a356e64 --- /dev/null +++ b/catkit/tests/gen/test1.1.3.py @@ -0,0 +1,22 @@ +from catkit.gen.surface import SlabGenerator +from ase.build import bulk +from ase import Atom + +bulk = bulk('Pd', 'fcc', a=5, cubic=True) +bulk[3].symbol = 'Cu' + +gen = SlabGenerator( + bulk, + miller_index=(1, 1, 1), + layers=6, + vacuum=10) + +atoms = gen.get_slab() +coordinates, connectivity = gen.adsorption_sites(atoms) + +atm = {1: 'X', 2: 'He', 3: 'F'} +for i, c in enumerate(coordinates): + typ = connectivity[i] + atoms += Atom(atm[typ], c + [0, 0, 2]) + +atoms.edit() diff --git a/catkit/tests/gen/test1.1_1.py b/catkit/tests/gen/test1.1_1.py new file mode 100644 index 00000000..59a2ac8c --- /dev/null +++ b/catkit/tests/gen/test1.1_1.py @@ -0,0 +1,22 @@ +from catkit.gen.surface import SlabGenerator +from ase.build import bulk +from ase.visualize import view + +# Make a test slab +atoms = bulk('Pd', 'fcc', cubic=True) +atoms[3].symbol = 'Cu' + +gen = SlabGenerator( + atoms, + miller_index=(2, 1, 1), + layers=9, + fixed=5, + vacuum=4) + +terminations = gen.get_unique_terminations() + +images = [] +for i, t in enumerate(terminations): + images += [gen.get_slab(iterm=i)] + +view(images) diff --git a/catkit/tests/gen/test1.1_2.py b/catkit/tests/gen/test1.1_2.py new file mode 100644 index 00000000..ea7b89eb --- /dev/null +++ b/catkit/tests/gen/test1.1_2.py @@ -0,0 +1,12 @@ +from catkit.build import surface +from ase.build import bulk + +# Make a test slab +atoms = bulk('Pd', 'fcc', cubic=True) +atoms[3].symbol = 'Cu' + +slab = surface(atoms, + size=(1, 1, 9), miller=(2, 1, 1), + vacuum=9, fixed=5) + +slab.edit() diff --git a/catkit/tests/gen/test1.2.1.py b/catkit/tests/gen/test1.2.1.py new file mode 100644 index 00000000..e795853a --- /dev/null +++ b/catkit/tests/gen/test1.2.1.py @@ -0,0 +1,18 @@ +from catkit.gen.adsorption import AdsorptionSites +from catkit.gen.surface import SlabGenerator +from ase.build import bulk + +bulk = bulk('Pd', 'fcc', a=5, cubic=True) +bulk[3].symbol = 'Cu' + +gen = SlabGenerator( + bulk, + miller_index=(1, 1, 1), + layers=6, + vacuum=4) + +atoms = gen.get_slab() +atoms.set_surface_atoms([8, 9, 10, 11]) + +sites = AdsorptionSites(atoms) +sites.plot('./Pd3Cu-adsorption-sites.png') diff --git a/catkit/tests/gen/test1.2.2.py b/catkit/tests/gen/test1.2.2.py new file mode 100644 index 00000000..0c7179a5 --- /dev/null +++ b/catkit/tests/gen/test1.2.2.py @@ -0,0 +1,45 @@ +from catkit.gen.adsorption import AdsorptionSites +from catkit.gen.surface import SlabGenerator +from ase.build import bulk +from ase.io import write +from ase import Atom + +bulk = bulk('Pd', 'fcc', a=5, cubic=True) +bulk[3].symbol = 'Cu' + +gen = SlabGenerator( + bulk, + miller_index=(3, 2, 1), + layers=13, + vacuum=5) + +atoms = gen.get_slab(size=1) + +sites = AdsorptionSites(atoms) + +# Positon of each site +coordinates = sites.get_coordinates() + +# Number of adjacent surface atoms +connectivity = sites.get_connectivity() + +# The indices of adjacent surface atoms +topology = sites.get_topology() + +# Only print every 5th entry. +print('Coordinates:\n', coordinates[::5], '\n') +print('Connectivity:\n', connectivity[::5], '\n') +print('Topology:\n', topology[::5], '\n') + +periodic = sites.get_periodic_sites() +print('Sites by periodicity:\n', periodic[::5], '\n') + +symmetric = sites.get_symmetric_sites() +print('Sites by symmetry:\n', symmetric[::5]) + +atm = {1: 'X', 2: 'He', 3: 'F', 4: 'N'} +for i, c in enumerate(coordinates): + typ = connectivity[i] + atoms += Atom(atm[typ], c + [0, 0, 2]) + +atoms.edit() diff --git a/catkit/tests/gen/test1.2.3.py b/catkit/tests/gen/test1.2.3.py new file mode 100644 index 00000000..c0e13256 --- /dev/null +++ b/catkit/tests/gen/test1.2.3.py @@ -0,0 +1,28 @@ +from catkit.gen.adsorption import AdsorptionSites +from catkit.gen.surface import SlabGenerator +from ase.build import bulk +from ase import Atom +import numpy as np + +bulk = bulk('Pd', 'fcc', a=5, cubic=True) +bulk[3].symbol = 'Cu' + +gen = SlabGenerator( + bulk, + miller_index=(2, 1, 1), + layers=10, + vacuum=5) + +atoms = gen.get_slab() +sites = AdsorptionSites(atoms) + +coordinates = sites.get_coordinates() +vectors = sites.get_adsorption_vectors() + +heights = np.arange(0, 2, 0.25) +for i, c in enumerate(coordinates): + for h in heights: + atoms += Atom('X', c + vectors[i] * h) + +atoms.wrap() +atoms.edit() diff --git a/catkit/tests/gen/test1.2.4.py b/catkit/tests/gen/test1.2.4.py new file mode 100644 index 00000000..1b984c62 --- /dev/null +++ b/catkit/tests/gen/test1.2.4.py @@ -0,0 +1,19 @@ +from catkit.gen.surface import SlabGenerator +from catkit.gen.adsorption import Builder +from ase.build import bulk +import numpy as np + +atoms = bulk('Pd', 'fcc', a=4, cubic=True) +atoms[3].symbol = 'Cu' + +gen = SlabGenerator( + atoms, + miller_index=(1, 1, 1), + layers=4, + fixed=2, + vacuum=10) + +slab = gen.get_slab() + +builder = Builder(slab) +print(builder) diff --git a/catkit/tests/gen/test1.2.4_1.py b/catkit/tests/gen/test1.2.4_1.py new file mode 100644 index 00000000..0b821e4c --- /dev/null +++ b/catkit/tests/gen/test1.2.4_1.py @@ -0,0 +1,23 @@ +from catkit.build import molecule +from catkit.gen.surface import SlabGenerator +from catkit.gen.adsorption import Builder +from ase.build import bulk + +atoms = bulk('Pd', 'fcc', a=4, cubic=True) +atoms[3].symbol = 'Cu' + +gen = SlabGenerator( + atoms, + miller_index=(1, 1, 1), + layers=4, + vacuum=4) + +slab = gen.get_slab() + +adsorbate = molecule('CH3')[0] +adsorbate.set_tags([-1, 0, 0, 0]) + +builder = Builder(slab) +ads_slab = builder.add_adsorbate(adsorbate, index=0) + +ads_slab.edit() diff --git a/catkit/tests/gen/test1.2.4_2.py b/catkit/tests/gen/test1.2.4_2.py new file mode 100644 index 00000000..115e5456 --- /dev/null +++ b/catkit/tests/gen/test1.2.4_2.py @@ -0,0 +1,24 @@ +from catkit.build import molecule +from catkit.gen.surface import SlabGenerator +from catkit.gen.adsorption import Builder +from ase.build import bulk +from ase.visualize import view + +atoms = bulk('Pd', 'fcc', a=4, cubic=True) +atoms[3].symbol = 'Cu' + +gen = SlabGenerator( + atoms, + miller_index=(1, 1, 1), + layers=4, + vacuum=4) + +slab = gen.get_slab() +adsorbate = molecule('C2H3')[1] + +builder = Builder(slab) +ads_slab = builder.add_adsorbate(adsorbate, bonds=[0, 1], index=-1) + +print('{} adsorption structures generated'.format(len(ads_slab))) + +view(ads_slab) diff --git a/catkit/tests/gen/test1.5.1.py b/catkit/tests/gen/test1.5.1.py new file mode 100644 index 00000000..8d3ea87e --- /dev/null +++ b/catkit/tests/gen/test1.5.1.py @@ -0,0 +1,28 @@ +from catkit.gen.route import get_response_reactions +import numpy as np + +epsilon = np.array([ + # To keep indexing consistent + [ 0, 0, 0, 0], # I1 + [ 0, 0, 0, 0], # I2 + [ 0, 0, 0, 0], # I3 + [ 0, 0, 0, 0], # I4 + [ 0, 0, 0, 0], # I5 + # C N H O + [ 1, 0, 4, 0], # CH4 + [ 0, 1, 0, 1], # NO + [ 0, 0, 0, 2], # O2 + [ 0, 2, 0, 0], # N2 + [ 1, 0, 0, 1], # CO + [ 1, 0, 0, 2], # CO2 + [ 0, 0, 2, 1], # H2O +]) + +terminal = [5, 6, 7, 8, 9, 10, 11] +OR, species = get_response_reactions(epsilon, terminal, species=True) + +print('Overall reaction routes:') +print(OR, '\n') + +print('Terminal species:') +print(species) diff --git a/catkit/tests/gen/test1.5.2.py b/catkit/tests/gen/test1.5.2.py new file mode 100644 index 00000000..b60d488c --- /dev/null +++ b/catkit/tests/gen/test1.5.2.py @@ -0,0 +1,51 @@ +from catkit.gen.route import get_response_reactions +from catkit.gen.route import get_heppel_sellers +import numpy as np + +nu = np.array([ + # H2Os, COs, CO2s, H2s, Hs, OHs, Os, HCOOs, H2O, CO, CO2, H2 + [ 0, 1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0], # s1 + [ 1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0], # s2 + [ 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 1, 0], # s3 + [ 0, 0, 0, 1, -2, 0, 0, 0, 0, 0, 0, 0], # s4 + [ 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 1], # s5 + [ -1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0], # s6 + [ 0, -1 , 1, 0, 0, 0, -1, 0, 0, 0, 0, 0], # s7 + [ 0, -1, 0, 0, 0, -1, 0, 1, 0, 0, 0, 0], # s8 + [ 0, 0, 0, 0, 1, -1, 1, 0, 0, 0, 0, 0], # s9 + [ 0, -1, 1, 0, 1, -1, 0, 0, 0, 0, 0, 0], # s10 + [ 0, 0, 1, 0, 1, 0, 0, -1, 0, 0, 0, 0], # s11 + [ 0, 0, 1, 0, 0, 1, -1, -1, 0, 0, 0, 0], # s12 + [ -1, 0, 0, 1, -1, 1, 0, 0, 0, 0, 0, 0], # s14 + [ 0, 0, 0, 1, -1, -1, 1, 0, 0, 0, 0, 0], # s15 + [ 0, 0, 1, 1, -1, 0, 0, -1, 0, 0, 0, 0], # s17 +]) + +epsilon = np.array([ + # Just a place holder + [ 0, 0, 0], # H2OS + [ 0, 0, 0], # COS + [ 0, 0, 0], # CO2S + [ 0, 0, 0], # H2S + [ 0, 0, 0], # HS + [ 0, 0, 0], # OHS + [ 0, 0, 0], # OS + [ 0, 0, 0], # HCOOS + # C, H, O + [ 0, 2, 1], # H2O + [ 1, 0, 1], # CO + [ 1, 0, 2], # CO2 + [ 0, 2, 0], # H2 +]) + +# Indices of the terminal species +terminal = [8, 9, 10, 11] + +RER, species = get_response_reactions(epsilon, terminal, species=True) +sigma = get_heppel_sellers(nu, species[0]) + +print('Linearly independent set of reaction routes:') +print(sigma, '\n') + +print('Overall reaction routes:') +print(np.dot(sigma, nu)) diff --git a/catkit/tests/gen/test1.5.3.py b/catkit/tests/gen/test1.5.3.py new file mode 100644 index 00000000..c26ecc94 --- /dev/null +++ b/catkit/tests/gen/test1.5.3.py @@ -0,0 +1,51 @@ +from catkit.gen.route import get_response_reactions +from catkit.gen.route import get_reaction_routes +from catkit.gen.route import get_heppel_sellers +import numpy as np +np.set_printoptions(threshold=np.inf) + +nu = np.array([ + [ 1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0], # s1 + [ 0, 1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0], # s2 + [ 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 1, 0], # s3 + [ 0, 0, 0, 1, -2, 0, 0, 0, 0, 0, 0, 0], # s4 + [ 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 1], # s5 + [ -1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0], # s6 + [ 0, -1, 1, 0, 0, 0, -1, 0, 0, 0, 0, 0], # s7 + [ 0, -1, 0, 0, 0, -1, 0, 1, 0, 0, 0, 0], # s8 + [ 0, 0, 0, 0, 1, -1, 1, 0, 0, 0, 0, 0], # s9 + [ 0, -1, 1, 0, 1, -1, 0, 0, 0, 0, 0, 0], # s10 + [ 0, 0, 1, 0, 1, 0, 0, -1, 0, 0, 0, 0], # s11 + [ 0, 0, 1, 0, 0, 1, -1, -1, 0, 0, 0, 0], # s12 + [ -1, 0, 0, 0, 0, 2, -1, 0, 0, 0, 0, 0], # s13 +]) + +epsilon = np.array([ + # C, H, O + [ 0, 2, 1], # SH2O + [ 1, 0, 1], # SCO + [ 1, 0, 2], # SCO2 + [ 0, 2, 0], # SH2 + [ 0, 1, 0], # SH + [ 0, 1, 1], # SOH + [ 0, 0, 1], # SO + [ 1, 1, 2], # SOOCH + [ 0, 2, 1], # H2O + [ 1, 0, 1], # CO + [ 1, 0, 2], # CO2 + [ 0, 2, 0], # H2 +]) + + +# Indices of the species considered terminal +terminal = [8, 9, 10, 11] + +RER, species = get_response_reactions(epsilon, terminal, species=True) +sigma = get_heppel_sellers(nu, species[0]) +FR, ER = get_reaction_routes(nu, sigma) + +print('{} Full reaction routes:'.format(len(FR))) +print(FR, '\n') + +print('{} Empty reaction routes:'.format(len(ER))) +print(ER) diff --git a/readme.org b/readme.org index f3638357..07992545 100644 --- a/readme.org +++ b/readme.org @@ -14,6 +14,13 @@ Welcome to CatKit! A staging ground for computational tools which are generally You can find our [[http://catkit.readthedocs.io/en/latest/?badge=latest][full documentation here]]. * Installation + +** Conda environment +It’s recommended to install the package in a virtual environment to avoid dependency conflicts. Python 3.10 has been tested with the required dependencies as of April 16, 2025. +#+BEGIN_SRC sh +conda create -n catkit python=3.10 +conda activate catkit +#+END_SRC ** Pip installation CatKit is most easily installed with pip using: diff --git a/requirements.txt b/requirements.txt index 876317ce..d643df57 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,9 @@ ase>=3.17 -numpy>=1.14 -networkx>=2.1 +numpy>=1.19 +networkx==2.8 spglib>=1.10 scipy>=0.1 -sklearn +scikit-learn matplotlib>=2.2 future>=0.16 fireworks>=1.7