Molecular identity#

automol.Identity wraps a string identifier (InChI, SMILES, …) together with the algorithm that produced it, so identifiers from different algorithms are never accidentally compared or mixed up.

Generating an identity from a Geometry#

from automol import Algorithm, Geometry, Identity

water = Geometry(
    symbols=["O", "H", "H"],
    coordinates=[[0.0, 0.0, 0.0], [0.0, 0.0, 0.96], [0.93, 0.0, -0.24]],
    charge=0,
    spin=0,
)

inchi = Identity.from_geometry(water, algorithm=Algorithm.RDKIT_INCHI)
smiles = Identity.from_geometry(water, algorithm=Algorithm.RDKIT_SMILES)

inchi.value       # "InChI=1S/H2O/h1H2"
inchi.algorithm   # Algorithm.RDKIT_INCHI
inchi.kind        # "stereoisomer"

If you already have a string identifier from elsewhere, wrap it directly with from_value instead of recomputing it:

inchi = Identity.from_value("InChI=1S/H2O/h1H2", algorithm=Algorithm.RDKIT_INCHI)

Going back to a Geometry#

Algorithms that support the inverse direction can reconstruct a Geometry from the identifier:

water_rt = inchi.geometry()

Calling .geometry() on an identity produced by an algorithm with no known inverse raises NotImplementedError.

kind#

Every Algorithm is tagged with a kind — a category describing what sort of identity it produces (currently "stereoisomer" for both built-in algorithms). Identity.kind is set automatically by from_geometry and from_value, and is validated against algorithm.kind on construction — an explicit mismatch raises a ValueError. This lets code group or dispatch on kind without hardcoding a specific algorithm.

How algorithms are implemented#

Algorithm is a closed StrEnum — each member is tagged with its kind at definition time, e.g. RDKIT_INCHI = ("rdkit inchi", "stereoisomer"). The behavior for each member is registered separately, via automol.ident.AlgorithmRegistry, by subclassing AlgorithmFns and decorating it:

from automol.ident import AlgorithmFns, AlgorithmRegistry

@AlgorithmRegistry.register(Algorithm.RDKIT_INCHI)
class RDKitInChI(AlgorithmFns):
    @staticmethod
    def identity_fn(geo: Geometry) -> str:
        ...  # Geometry -> InChI

    @staticmethod
    def geometry_fn(value: str) -> Geometry:
        ...  # InChI -> Geometry

geometry_fn is optional — omit it (or fall back to AlgorithmFns’s default) for an algorithm that only supports the forward direction; calling .geometry() on such an identity raises NotImplementedError, as above.

This split — a fixed enum of what algorithms exist, plus a registry of how each one behaves — is what RDKitInChI and RDKitSMILES in automol.ident use, and is the pattern to follow when adding a new algorithm to automol itself (which means adding a new Algorithm member alongside its AlgorithmFns implementation, both within automol.ident).

Registering an algorithm twice raises automol.utils.exc.AlgorithmAlreadyRegisteredError; looking up one that was never registered raises automol.utils.exc.UnknownAlgorithmError.