Features¶
Feature sites of hosts and guests (functional groups, pi rings, H-bond sites, open metal sites). Used by CageBuilder.get_features(): cage_isomer_builder.utils.features.
Chemical feature sites of a host (cage, MOF, COF) or a guest molecule.
A feature is a point with a type and, where it has one, a direction:
============== ================================= ==================================
kind position vector
============== ================================= ==================================
fg functional-group site (R site) ring carbon -> site, unit (directed)
pi aromatic ring centroid ring normal (axis: sign-less)
donor the H of an O-H / N-H heavy atom -> H, unit (directed)
acceptor an O or N with a free lone pair lone-pair bisector, unit (directed)
open_metal a metal below its full direction of the empty
coordination coordination site (directed)
============== ================================= ==================================
The same detector runs on hosts and guests, so their descriptors can be
compared directly (see :mod:cage_isomer_builder.utils.matching).
Rules (kept simple, so a result can be checked by hand):
- Bonds are perceived from covalent radii (
natural_cutoffsx 1.2), except metal-ligand contacts, which use x 1.15 so a carboxylate C next to a metal is not counted as a ligand atom. R-site markers (X) are bonded to their nearest heavy atom. - Aromatic rings are 5- and 6-membered cycles of C/N/O/S/B atoms, each with at most three neighbours, lying in a plane (RMS deviation below 0.15 Angstrom).
- Donors are H atoms bonded to O or N. A donor whose O/N is bonded to two
or more metals is labelled
mu<n>-OH(e.g. the mu3-OH of a Zr6 node). - Acceptors are O atoms, and N atoms that are not three-coordinate and
planar (amide/aromatic-substituted N). O and N bonded to a metal and to
a non-metal (e.g. a metal-bound carboxylate O) are excluded by default,
being coordinatively saturated; metal-only O (oxo, mu3-O) are kept and
labelled
mu<n>-O. - Open metal sites: by default mofstructure's geometric detector
(Chung et al., the CoRE MOF method): a metal is open when its coordination
sphere is an incomplete polyhedron (an octahedron, square antiprism, ...
with a site missing), so every Cu of a bare paddlewheel is open and a
defect-free UiO-66 has none. The alternative
method="coordination"compares coordination numbers: a metal is open belowexpected_cnfor its element, or below the most highly coordinated metal of that element in the structure. The direction is minus the sum of the unit vectors to the ligands. - FG orientation (endo/exo): the cosine between the ring-C -> site bond and
the direction from that carbon to the pore centre.
endoif abovethreshold(default 0.5, i.e. within 60 degrees),exoif below-threshold, elsetangential(e.g. every H of a ring lying face-on to the pore). Periodic structures have no single pore centre unless one is given.
FeatureSet
dataclass
¶
FeatureSet(positions: ndarray, vectors: ndarray, directed: ndarray, kinds: ndarray, labels: ndarray, orientation: ndarray, owners: ndarray, atom_indices: ndarray, cell: ndarray = (lambda: np.zeros((3, 3)))(), pbc: ndarray = (lambda: np.zeros(3, dtype=bool))())
Feature sites, as parallel arrays (one row per feature).
Attributes:
| Name | Type | Description |
|---|---|---|
positions |
(ndarray, shape(n, 3))
|
|
vectors |
(ndarray, shape(n, 3))
|
Unit direction of each feature, or zeros if it has none. |
directed |
np.ndarray of bool, shape (n,)
|
False for a sign-less axis (a ring normal), so angles to it are folded into [0, 90] degrees. |
kinds |
np.ndarray of str
|
One of :data: |
labels |
np.ndarray of str
|
Finer chemical label: R-group name for |
orientation |
np.ndarray of str
|
|
owners |
np.ndarray of int
|
Building block (linker/node) each feature belongs to, -1 if unknown. Pairs inside one building block can be excluded from descriptors. |
atom_indices |
np.ndarray of int
|
A representative atom of the feature in the source structure (site, H, acceptor, metal, first ring atom). |
cell |
(ndarray, shape(3, 3))
|
|
pbc |
np.ndarray of bool, shape (3,)
|
|
Examples:
>>> from ase.build import molecule
>>> from cage_isomer_builder.utils.features import detect_features
>>> feats = detect_features(molecule("H2O"))
>>> len(feats), sorted(feats.kinds.tolist())
(3, ['acceptor', 'donor', 'donor'])
>>> feats.counts()
{'acceptor': 1, 'donor': 2}
empty
classmethod
¶
A feature set with no features (optionally carrying a cell).
Examples:
Source code in cage_isomer_builder/utils/features.py
from_rows
classmethod
¶
Build from dicts with keys position, vector, directed, kind, label, orientation, owner, atom.
Examples:
>>> from cage_isomer_builder.utils.features import FeatureSet
>>> fs = FeatureSet.from_rows([
... dict(position=[0, 0, 0], vector=[0, 0, 1], directed=False, kind="pi", label="C6"),
... dict(position=[3.5, 0, 0], kind="acceptor", label="O"),
... ])
>>> fs.kinds.tolist(), fs.directed.tolist()
(['pi', 'acceptor'], [False, True])
Source code in cage_isomer_builder/utils/features.py
subset ¶
Features where mask (bool array or index list) is selected.
Examples:
>>> from ase.build import molecule
>>> from cage_isomer_builder.utils.features import detect_features
>>> feats = detect_features(molecule("H2O"))
>>> len(feats.subset([0, 1]))
2
Source code in cage_isomer_builder/utils/features.py
select ¶
Features of the given kind(s) and/or label(s).
Examples:
>>> from ase.build import molecule
>>> from cage_isomer_builder.utils.features import detect_features
>>> feats = detect_features(molecule("C5H5N")) # pyridine
>>> feats.select(kinds="acceptor").labels.tolist()
['N']
Source code in cage_isomer_builder/utils/features.py
counts ¶
{kind: number of features}.
Examples:
>>> from ase.build import molecule
>>> from cage_isomer_builder.utils.features import detect_features
>>> detect_features(molecule("NH3")).counts()
{'acceptor': 1, 'donor': 3}
Source code in cage_isomer_builder/utils/features.py
is_metal ¶
bond_graph ¶
Adjacency lists {i: [j, ...]} from covalent radii.
Metal-ligand contacts use the tighter metal_mult; metal-metal
contacts are left out (they are not coordination bonds). Each R-site
marker (X) is bonded to its nearest heavy atom only.
Examples:
>>> from ase.build import molecule
>>> from cage_isomer_builder.utils.features import bond_graph
>>> graph = bond_graph(molecule("H2O")) # atoms: O, H, H
>>> sorted(graph[0])
[1, 2]
Source code in cage_isomer_builder/utils/features.py
adjacency_from_bond_matrix ¶
Adjacency lists from a known bond-order matrix (e.g. STK's), leaving
out metal-metal entries. Use this instead of :func:bond_graph
whenever the true bonds are known: a built but unoptimised structure
can have bonds far longer than any distance cutoff.
Examples:
>>> import numpy as np
>>> from ase.build import molecule
>>> from cage_isomer_builder.utils.features import adjacency_from_bond_matrix
>>> water = molecule("H2O")
>>> bonds = np.array([[0, 1, 1], [1, 0, 0], [1, 0, 0]])
>>> adjacency_from_bond_matrix(water, bonds)
{0: [1, 2], 1: [0], 2: [0]}
Source code in cage_isomer_builder/utils/features.py
find_rings ¶
Every simple cycle of the given sizes among ring-forming atoms (C/N/O/S/B, at most three neighbours), each as a tuple of atom indices in ring order, starting at its lowest index.
Examples:
>>> from ase.build import molecule
>>> from cage_isomer_builder.utils.features import find_rings
>>> find_rings(molecule("C6H6"))
[(0, 1, 2, 3, 4, 5)]
Source code in cage_isomer_builder/utils/features.py
ring_geometry ¶
Centroid, unit normal and RMS out-of-plane deviation of a ring (unwrapped across periodic boundaries).
Examples:
>>> import numpy as np
>>> from ase.build import molecule
>>> from cage_isomer_builder.utils.features import find_rings, ring_geometry
>>> benzene = molecule("C6H6")
>>> centroid, normal, rms = ring_geometry(benzene, find_rings(benzene)[0])
>>> np.round(np.abs(normal), 3).tolist(), rms < 1e-6 # flat ring in the xy plane
([0.0, 0.0, 1.0], True)
Source code in cage_isomer_builder/utils/features.py
aromatic_rings ¶
Rings from :func:find_rings that are planar within planarity
(RMS, Angstrom).
Examples:
>>> from ase.build import molecule
>>> from cage_isomer_builder.utils.features import aromatic_rings
>>> len(aromatic_rings(molecule("C6H6"))), len(aromatic_rings(molecule("C2H4")))
(1, 0)
Source code in cage_isomer_builder/utils/features.py
pi_features ¶
One pi feature per aromatic ring: centroid + ring normal.
Examples:
>>> from ase.build import molecule
>>> from cage_isomer_builder.utils.features import pi_features
>>> rings = pi_features(molecule("C5H5N"))
>>> rings.labels.tolist(), bool(rings.directed[0])
(['CN6'], False)
Source code in cage_isomer_builder/utils/features.py
hbond_features ¶
donor and acceptor features (see the module rules).
Examples:
>>> from ase.build import molecule
>>> from cage_isomer_builder.utils.features import hbond_features
>>> hbond_features(molecule("CH3CONH2")).counts() # the planar amide N is no acceptor
{'acceptor': 1, 'donor': 2}
Source code in cage_isomer_builder/utils/features.py
coordination_numbers ¶
{metal index: number of bonded non-metal, non-site atoms}.
Examples:
>>> from cage_isomer_builder import example_data
>>> sbu = example_data.read("uio66_sbu")
>>> sbu = sbu[[a.index for a in sbu if a.symbol != "X"]] # drop connection markers
>>> from cage_isomer_builder.utils.features import coordination_numbers
>>> sorted(set(coordination_numbers(sbu).values())) # every Zr is 8-coordinate
[8]
Source code in cage_isomer_builder/utils/features.py
geometric_open_metals ¶
Open-metal-site analysis of every metal by mofstructure's detector
(omsdetector_forked, Chung et al., the CoRE MOF open-metal-site
method): each metal's first coordination sphere is checked against the
ideal closed polyhedra, so a site is open when part of its coordination
shell is missing, whether or not other metals of the same element are
more highly coordinated (e.g. every Cu of a bare paddlewheel is open).
Finite structures are placed in a box padding Angstrom larger than
the structure on every side, so no periodic image is within bonding
reach. R-site markers (X) are left out.
Returns:
| Type | Description |
|---|---|
dict
|
|
Examples:
>>> from cage_isomer_builder import example_data
>>> sbu = example_data.read("uio66_sbu")
>>> sbu = sbu[[a.index for a in sbu if a.symbol != "X"]] # drop connection markers
>>> from cage_isomer_builder.utils.features import geometric_open_metals
>>> result = geometric_open_metals(sbu)
>>> any(is_open for is_open, *_ in result.values())
False
Source code in cage_isomer_builder/utils/features.py
open_metal_features ¶
open_metal features.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
method
|
(auto, geometry, coordination)
|
|
"auto"
|
Examples:
>>> from cage_isomer_builder import example_data
>>> sbu = example_data.read("uio66_sbu")
>>> sbu = sbu[[a.index for a in sbu if a.symbol != "X"]] # drop connection markers
>>> from cage_isomer_builder.utils.features import bond_graph, open_metal_features
>>> graph = bond_graph(sbu)
>>> carbon = next(a.index for a in sbu if a.symbol == "C")
>>> gone = [carbon] + [j for j in graph[carbon] if sbu[j].symbol == "O"]
>>> defect = sbu[[i for i in range(len(sbu)) if i not in gone]] # remove one carboxylate
>>> open_metal_features(defect).labels.tolist()
['Zr(CN=7)', 'Zr(CN=7)']
Source code in cage_isomer_builder/utils/features.py
orientation_class ¶
"endo" / "exo" / "tangential" from the cosine between a
site bond and the inward direction.
Examples:
>>> from cage_isomer_builder.utils.features import orientation_class
>>> orientation_class(0.9), orientation_class(-0.8), orientation_class(0.1)
('endo', 'exo', 'tangential')
Source code in cage_isomer_builder/utils/features.py
site_orientation ¶
Cosine and class (:func:orientation_class) of each site bond with
respect to centre.
Examples:
>>> from ase.build import molecule
>>> from cage_isomer_builder.utils.features import site_orientation
>>> benzene = molecule("C6H6") # H 6 is bonded to C 0
>>> centre = 3 * benzene.positions[6] - 2 * benzene.positions[0] # out along C-H
>>> cosines, classes = site_orientation(benzene, [6], [0], centre)
>>> classes
['endo']
Source code in cage_isomer_builder/utils/features.py
fg_features ¶
fg_features(atoms, slot_indices=None, adjacency=None, owners=None, centre=None, threshold=0.5, labels=None)
One fg feature per R site (slot), in slot order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
slot_indices
|
sequence of int
|
Site atoms in slot order. Default: every site atom of |
None
|
centre
|
array - like
|
Pore centre for endo/exo. Default: for a finite structure, the centroid of its non-H, non-site atoms; periodic structures get no orientation unless a centre is given. |
None
|
labels
|
sequence of str
|
Label per slot (default |
None
|
Examples:
>>> from ase.build import molecule
>>> from cage_isomer_builder.utils.sites import mark_site
>>> from cage_isomer_builder.utils.features import fg_features
>>> benzene = molecule("C6H6")
>>> mark_site(benzene, 6, 1)
>>> mark_site(benzene, 9, 2)
>>> fg_features(benzene).labels.tolist()
['R1', 'R2']
Source code in cage_isomer_builder/utils/features.py
detect_features ¶
detect_features(atoms, kinds=('pi', 'donor', 'acceptor', 'open_metal'), owners=None, expected_cn=None, include_metal_bound=False, centre=None, threshold=0.5, slot_indices=None, adjacency=None, open_metal_method='auto')
All features of the requested kinds ("fg" needs R sites on
atoms). Works on hosts and guest molecules alike.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
atoms
|
Atoms
|
|
required |
kinds
|
sequence of str
|
Subset of :data: |
('pi', 'donor', 'acceptor', 'open_metal')
|
owners
|
list of list of int
|
Atom indices of each building block; sets |
None
|
expected_cn
|
dict
|
|
None
|
open_metal_method
|
(auto, geometry, coordination)
|
See :func: |
"auto"
|
include_metal_bound
|
bool
|
Keep O/N acceptors bonded to both a metal and a non-metal. |
False
|
centre
|
For |
None
|
|
threshold
|
For |
None
|
|
slot_indices
|
For |
None
|
Examples:
>>> from cage_isomer_builder import example_data
>>> from cage_isomer_builder.utils.features import detect_features
>>> detect_features(example_data.read("ibuprofen")).counts()
{'acceptor': 2, 'donor': 1, 'pi': 1}