Skip to content

stereochemistry

Stereochemistry detection and encoding.

Implements CIP (Cahn-Ingold-Prelog) rules for: - R/S configuration at tetrahedral stereocenters - E/Z configuration at double bonds

DoubleBondStereo dataclass

Represents E/Z stereochemistry at a double bond.

substituents1 / substituents2 list the high-priority neighbour first for each end of the bond. configuration is "E", "Z", or None if undetermined.

Source code in chmpy/graph/stereochemistry.py
@dataclass
class DoubleBondStereo:
    """Represents E/Z stereochemistry at a double bond.

    ``substituents1`` / ``substituents2`` list the high-priority neighbour
    first for each end of the bond. ``configuration`` is ``"E"``, ``"Z"``,
    or ``None`` if undetermined.
    """

    atom1: int
    atom2: int
    substituents1: list[int]
    substituents2: list[int]
    configuration: str | None = None

StereoCenter dataclass

Represents a tetrahedral stereocenter.

neighbors is in priority order (high to low). configuration is "R", "S", or None if undetermined.

Source code in chmpy/graph/stereochemistry.py
@dataclass
class StereoCenter:
    """Represents a tetrahedral stereocenter.

    ``neighbors`` is in priority order (high to low). ``configuration`` is
    ``"R"``, ``"S"``, or ``None`` if undetermined.
    """

    atom_idx: int
    neighbors: list[int]
    configuration: str | None = None

assign_stereochemistry(graph)

Detect and assign stereochemistry for a molecular graph.

Parameters:

Name Type Description Default
graph MolecularGraph

A MolecularGraph with 3D coordinates.

required

Returns:

Type Description
tuple[list[StereoCenter], list[DoubleBondStereo]]

Tuple of (stereocenters, double_bond_stereo) lists.

Source code in chmpy/graph/stereochemistry.py
def assign_stereochemistry(
    graph: "MolecularGraph",
) -> tuple[list[StereoCenter], list[DoubleBondStereo]]:
    """
    Detect and assign stereochemistry for a molecular graph.

    Args:
        graph: A MolecularGraph with 3D coordinates.

    Returns:
        Tuple of (stereocenters, double_bond_stereo) lists.
    """
    stereocenters = find_stereocenters(graph)
    double_bonds = find_double_bond_stereo(graph)

    # Assign configurations
    for sc in stereocenters:
        sc.configuration = _tetrahedral_stereo(graph, sc.atom_idx, sc.neighbors)

    for db in double_bonds:
        db.configuration = _double_bond_stereo(
            graph, db.atom1, db.atom2, db.substituents1, db.substituents2
        )

    return stereocenters, double_bonds

count_stereocenters(graph)

Count the number of stereocenters in the molecule.

Parameters:

Name Type Description Default
graph MolecularGraph

A MolecularGraph.

required

Returns:

Type Description
int

Number of tetrahedral stereocenters.

Source code in chmpy/graph/stereochemistry.py
def count_stereocenters(graph: "MolecularGraph") -> int:
    """
    Count the number of stereocenters in the molecule.

    Args:
        graph: A MolecularGraph.

    Returns:
        Number of tetrahedral stereocenters.
    """
    return len(find_stereocenters(graph))

find_double_bond_stereo(graph)

Find double bonds that can exhibit E/Z stereochemistry.

Parameters:

Name Type Description Default
graph MolecularGraph

A MolecularGraph.

required

Returns:

Type Description
list[DoubleBondStereo]

List of DoubleBondStereo objects (without configuration assigned).

Source code in chmpy/graph/stereochemistry.py
def find_double_bond_stereo(graph: "MolecularGraph") -> list[DoubleBondStereo]:
    """
    Find double bonds that can exhibit E/Z stereochemistry.

    Args:
        graph: A MolecularGraph.

    Returns:
        List of DoubleBondStereo objects (without configuration assigned).
    """
    double_bonds = []

    for i, j in graph.edges():
        if not is_double_bond(graph, i, j):
            continue

        # Get substituents on each carbon
        neighbors_i = [n for n in graph.neighbors(i) if n != j]
        neighbors_j = [n for n in graph.neighbors(j) if n != i]

        # Need at least 2 substituents total (one on each carbon)
        if len(neighbors_i) < 1 or len(neighbors_j) < 1:
            continue

        # Check for equivalent substituents on same carbon
        priorities_i = [_cip_priority(graph, n, i) for n in neighbors_i]
        priorities_j = [_cip_priority(graph, n, j) for n in neighbors_j]

        # If both substituents on one carbon are the same, no E/Z possible
        if len(neighbors_i) == 2 and priorities_i[0] == priorities_i[1]:
            continue
        if len(neighbors_j) == 2 and priorities_j[0] == priorities_j[1]:
            continue

        # Sort by priority
        sorted_i = [
            n for _, n in sorted(zip(priorities_i, neighbors_i, strict=False), reverse=True)
        ]
        sorted_j = [
            n for _, n in sorted(zip(priorities_j, neighbors_j, strict=False), reverse=True)
        ]

        double_bonds.append(DoubleBondStereo(i, j, sorted_i, sorted_j))

    return double_bonds

find_stereocenters(graph)

Find potential tetrahedral stereocenters in the graph.

A stereocenter is an sp3 atom with 4 different substituents.

Parameters:

Name Type Description Default
graph MolecularGraph

A MolecularGraph.

required

Returns:

Type Description
list[StereoCenter]

List of StereoCenter objects (without configuration assigned).

Source code in chmpy/graph/stereochemistry.py
def find_stereocenters(graph: "MolecularGraph") -> list[StereoCenter]:
    """
    Find potential tetrahedral stereocenters in the graph.

    A stereocenter is an sp3 atom with 4 different substituents.

    Args:
        graph: A MolecularGraph.

    Returns:
        List of StereoCenter objects (without configuration assigned).
    """
    stereocenters = []

    for atom_idx in range(graph.n_atoms):
        neighbors = list(graph.neighbors(atom_idx))

        # Must have exactly 4 neighbors for tetrahedral center
        if len(neighbors) != 4:
            continue

        # Check if all substituents are different
        priorities = [_cip_priority(graph, n, atom_idx) for n in neighbors]

        # If any two have identical priority, not a stereocenter
        if len(set(priorities)) != 4:
            continue

        # Sort neighbors by CIP priority (high to low)
        sorted_neighbors = [
            n for _, n in sorted(zip(priorities, neighbors, strict=False), reverse=True)
        ]

        stereocenters.append(StereoCenter(atom_idx, sorted_neighbors))

    return stereocenters

get_double_bond_config(graph, atom1, atom2)

Get the E/Z configuration of a double bond.

Parameters:

Name Type Description Default
graph MolecularGraph

A MolecularGraph.

required
atom1 int

First atom of the double bond.

required
atom2 int

Second atom of the double bond.

required

Returns:

Type Description
str | None

"E", "Z", or None if not a stereogenic double bond.

Source code in chmpy/graph/stereochemistry.py
def get_double_bond_config(
    graph: "MolecularGraph", atom1: int, atom2: int
) -> str | None:
    """
    Get the E/Z configuration of a double bond.

    Args:
        graph: A MolecularGraph.
        atom1: First atom of the double bond.
        atom2: Second atom of the double bond.

    Returns:
        "E", "Z", or None if not a stereogenic double bond.
    """
    if not is_double_bond(graph, atom1, atom2):
        return None

    neighbors1 = [n for n in graph.neighbors(atom1) if n != atom2]
    neighbors2 = [n for n in graph.neighbors(atom2) if n != atom1]

    if len(neighbors1) < 1 or len(neighbors2) < 1:
        return None

    priorities1 = [_cip_priority(graph, n, atom1) for n in neighbors1]
    priorities2 = [_cip_priority(graph, n, atom2) for n in neighbors2]

    if len(neighbors1) == 2 and priorities1[0] == priorities1[1]:
        return None
    if len(neighbors2) == 2 and priorities2[0] == priorities2[1]:
        return None

    sorted1 = [n for _, n in sorted(zip(priorities1, neighbors1, strict=False), reverse=True)]
    sorted2 = [n for _, n in sorted(zip(priorities2, neighbors2, strict=False), reverse=True)]

    return _double_bond_stereo(graph, atom1, atom2, sorted1, sorted2)

get_stereocenter_config(graph, atom_idx)

Get the R/S configuration of a single atom if it's a stereocenter.

Parameters:

Name Type Description Default
graph MolecularGraph

A MolecularGraph.

required
atom_idx int

Index of the atom to check.

required

Returns:

Type Description
str | None

"R", "S", or None if not a stereocenter.

Source code in chmpy/graph/stereochemistry.py
def get_stereocenter_config(
    graph: "MolecularGraph", atom_idx: int
) -> str | None:
    """
    Get the R/S configuration of a single atom if it's a stereocenter.

    Args:
        graph: A MolecularGraph.
        atom_idx: Index of the atom to check.

    Returns:
        "R", "S", or None if not a stereocenter.
    """
    neighbors = list(graph.neighbors(atom_idx))

    if len(neighbors) != 4:
        return None

    priorities = [_cip_priority(graph, n, atom_idx) for n in neighbors]
    if len(set(priorities)) != 4:
        return None

    sorted_neighbors = [
        n for _, n in sorted(zip(priorities, neighbors, strict=False), reverse=True)
    ]

    return _tetrahedral_stereo(graph, atom_idx, sorted_neighbors)

is_chiral(graph)

Check if the molecule has any stereocenters.

Parameters:

Name Type Description Default
graph MolecularGraph

A MolecularGraph.

required

Returns:

Type Description
bool

True if the molecule has at least one stereocenter.

Source code in chmpy/graph/stereochemistry.py
def is_chiral(graph: "MolecularGraph") -> bool:
    """
    Check if the molecule has any stereocenters.

    Args:
        graph: A MolecularGraph.

    Returns:
        True if the molecule has at least one stereocenter.
    """
    return len(find_stereocenters(graph)) > 0