stk.cage.Cage

class stk.cage.Cage(building_blocks, vertex_alignments=None, vertex_positions=None, reaction_factory=GenericReactionFactory(), num_processes=1, optimizer=<stk._internal.optimizers.null.NullOptimizer object>, scale_multiplier=1.0)[source]

Bases: TopologyGraph

Represents a cage topology graph.

Notes

Cage topologies are added by creating a subclass, which defines the _vertex_prototypes and _edge_prototypes class attributes.

Examples

Subclass Implementation

The source code of the subclasses, listed in cage, can serve as good examples.

Basic Construction

Cage instances can be made by providing the building block molecules only (using FourPlusSix as an example)

import stk

bb1 = stk.BuildingBlock(
    smiles='NCCN',
    functional_groups=[stk.PrimaryAminoFactory()],
)
bb2 = stk.BuildingBlock(
    smiles='O=CC(C=O)C=O',
    functional_groups=[stk.AldehydeFactory()],
)
cage = stk.ConstructedMolecule(
    topology_graph=stk.cage.FourPlusSix((bb1, bb2)),
)

Suggested Optimization

For Cage topologies, it is recommend to use the MCHammer optimizer. However, for cages formed from highly unsymmetrical building blocks, it is recommend to use the simplified Collapser optimizer.

import stk

bb1 = stk.BuildingBlock(
    smiles='NCCN',
    functional_groups=[stk.PrimaryAminoFactory()],
)
bb2 = stk.BuildingBlock(
    smiles='O=CC(C=O)C=O',
    functional_groups=[stk.AldehydeFactory()],
)

cage = stk.ConstructedMolecule(
    topology_graph=stk.cage.FourPlusSix(
        building_blocks=(bb1, bb2),
        optimizer=stk.MCHammer(),
    ),
)

Structural Isomer Construction

Different structural isomers of cages can be made by using the vertex_alignments optional parameter

import stk

bb1 = stk.BuildingBlock(
    smiles='NCCN',
    functional_groups=[stk.PrimaryAminoFactory()],
)
bb2 = stk.BuildingBlock(
    smiles='O=CC(C=O)C=O',
    functional_groups=[stk.AldehydeFactory()],
)

cage = stk.ConstructedMolecule(
    topology_graph=stk.cage.FourPlusSix(
        building_blocks=(bb1, bb2),
        vertex_alignments={0: 1, 1: 1, 2: 2},
    ),
)

The parameter maps the id of a vertex to a number between 0 (inclusive) and the number of edges the vertex is connected to (exclusive). So a vertex connected to three edges can be mapped to 0, 1 or 2.

By changing which edge each vertex is aligned with, a different structural isomer of the cage can be formed.

Multi-Building Block Cage Construction

You can also build cages with multiple building blocks, but, if you have multiple building blocks with the same number of functional groups, you have to assign each building block to the vertex you want to place it on

import stk

bb1 = stk.BuildingBlock(
    smiles='O=CC(C=O)C=O',
    functional_groups=[stk.AldehydeFactory()],
)
bb2 = stk.BuildingBlock(
    smiles='O=CC(Cl)(C=O)C=O',
    functional_groups=[stk.AldehydeFactory()],
)
bb3 = stk.BuildingBlock(
    smiles='NCCN',
    functional_groups=[stk.PrimaryAminoFactory()],
)
bb4 = stk.BuildingBlock(
    smiles='NCC(Cl)N',
    functional_groups=[stk.PrimaryAminoFactory()],
)
bb5 = stk.BuildingBlock(
    smiles='NCCCCN',
    functional_groups=[stk.PrimaryAminoFactory()],
)

cage1 = stk.ConstructedMolecule(
    topology_graph=stk.cage.FourPlusSix(
        building_blocks={
            bb1: range(2),
            bb2: (2, 3),
            bb3: 4,
            bb4: 5,
            bb5: range(6, 10),
        },
    ),
)

You can combine this with the vertex_alignments parameter

cage2 = stk.ConstructedMolecule(
    topology_graph=stk.cage.FourPlusSix(
        building_blocks={
            bb1: range(2),
            bb2: (2, 3),
            bb3: 4,
            bb4: 5,
            bb5: range(6, 10),
        },
        vertex_alignments={5: 1},
    ),
)

Construction with Custom Vertex Positions

For Cage topologies, it is possible to redefine the vertex positions by hand with the vertex_positions argument.

The parameter maps the id of a vertex to a numpy array for its new position. The alignment should be modifed to match the new vertex position.

It is possible to change some or all vertex positions.

Consider that the vertex positions that are provided by the user are not scaled like the default ideal topology positions. Additionally, existing placement rules for other vertices are maintained; particularly, the effect of vertex.init_at_center.

import stk
import numpy as np

bb1 = stk.BuildingBlock(
    smiles='NCCN',
    functional_groups=stk.PrimaryAminoFactory(),
)
bb2 = stk.BuildingBlock(
    smiles='O=CC(C=O)C=O',
    functional_groups=stk.AldehydeFactory(),
)

cage = stk.ConstructedMolecule(
    topology_graph=stk.cage.FourPlusSix(
        building_blocks=[bb1, bb2],
        # Build tetrahedron with tilt.
        vertex_positions={
            0: 5 * np.array([0, 1.5, 1.2]),
            1: 5 * np.array([-1, -0.6, -0.41]),
            2: 5 * np.array([1, -0.6, -0.41]),
            3: 5 * np.array([0, 1.2, -0.41]),
        },
    ),
)

Metal-Organic Cage Construction

A series of common metal-organic cage topologies are provided and can be constructed in the same way as other Cage instances using metal atoms and DativeReactionFactory instances to produce metal-ligand bonds. Each metal topology has specific vertices reserved for the metal atoms or complexes, which are listed in their documentation.

import stk

# Produce a Pd+2 atom with 4 functional groups.
palladium_atom = stk.BuildingBlock(
    smiles='[Pd+2]',
    functional_groups=(
        stk.SingleAtom(stk.Pd(0, charge=2))
        for i in range(4)
    ),
    position_matrix=[[0., 0., 0.]],
)

# Build a building block with two functional groups using
# the SmartsFunctionalGroupFactory.
bb1 = stk.BuildingBlock(
    smiles=(
        'C1=NC=CC(C2=CC=CC(C3=C'
        'C=NC=C3)=C2)=C1'
    ),
    functional_groups=[
        stk.SmartsFunctionalGroupFactory(
            smarts='[#6]~[#7X2]~[#6]',
            bonders=(1, ),
            deleters=(),
        ),
    ],
)

cage1 = stk.ConstructedMolecule(
    stk.cage.M2L4Lantern(
        building_blocks=(palladium_atom, bb1),
        # Ensure that bonds between the
        # GenericFunctionalGroups of the ligand and the
        # SingleAtom functional groups of the metal are
        # dative.
        reaction_factory=stk.DativeReactionFactory(
            stk.GenericReactionFactory(
                bond_orders={
                    frozenset({
                        stk.GenericFunctionalGroup,
                        stk.SingleAtom,
                    }): 9,
                },
            ),
        ),
    ),
)

Controlling Metal-Complex Stereochemistry

When building metal-organic cages from octahedral metals, i.e. Fe(II), the stereochemistry of the metal centre can be important. Maintaining that stereochemistry around specific metal centres during Cage construction is difficult, so an alternative route to these types of structures can be taken. Firstly, you would construct a MetalComplex instance with the appropriate stereochemistry and dummy reactive groups (bromine in the following example)

import stk

# Produce a Fe+2 atom with 6 functional groups.
iron_atom = stk.BuildingBlock(
    smiles='[Fe+2]',
    functional_groups=(
        stk.SingleAtom(stk.Fe(0, charge=2))
        for i in range(6)
    ),
    position_matrix=[[0, 0, 0]],
)

# Define coordinating ligand with dummy bromine groups and
# metal coordinating functional groups.
bb2 = stk.BuildingBlock(
    smiles='C1=NC(C=NBr)=CC=C1',
    functional_groups=[
        stk.SmartsFunctionalGroupFactory(
            smarts='[#6]~[#7X2]~[#35]',
            bonders=(1, ),
            deleters=(),
        ),
        stk.SmartsFunctionalGroupFactory(
            smarts='[#6]~[#7X2]~[#6]',
            bonders=(1, ),
            deleters=(),
        ),
    ],
)

# Build iron complex with delta stereochemistry.
iron_oct_delta = stk.ConstructedMolecule(
    topology_graph=stk.metal_complex.OctahedralDelta(
        metals=iron_atom,
        ligands=bb2,
    ),
)

Then the metal complexes can be placed on the appropriate Cage topology to produce a structure with the desired stereochemistry at all metal centres.

# Assign Bromo functional groups to the metal complex.
iron_oct_delta = stk.BuildingBlock.init_from_molecule(
    molecule=iron_oct_delta,
    functional_groups=[stk.BromoFactory()],
)

# Define spacer building block.
bb3 = stk.BuildingBlock(
    smiles=(
        'C1=CC(C2=CC=C(Br)C=C2)=C'
        'C=C1Br'
    ),
    functional_groups=[stk.BromoFactory()],
)

# Build an M4L6 Tetrahedron with a spacer.
cage2 = stk.ConstructedMolecule(
    topology_graph=stk.cage.M4L6TetrahedronSpacer(
        building_blocks=(
            iron_oct_delta,
            bb3,
        ),
    ),
)

Aligning Metal Complex Building Blocks

When building metal-organic cages from metal complex building blocks, it is common that the metal complex BuildingBlock will have multiple functional groups, but that those functional groups are overlapping. This means that some of its atoms appear in multiple functional groups. A difficulty arises when the atom shared between the functional groups is a placer atom.

Placer atoms are used to align building blocks, so that they have an appropriate orientation in the final topology. If there is only one placer atom, no alignment can be made, as no vector running between placer atoms can be defined, and used for the alignment of the BuildingBlock.

By default, stk may create overlapping functional groups, which may lead to a lack of an appropriate number of placer atoms, leading to a BuildingBlock being unaligned. However, the user can manually set the placer atoms of functional groups, so that not all of the placer atoms appear in multiple functional groups, which leads to proper alignment.

First we build a metal complex

import stk

metal_atom = stk.BuildingBlock(
    smiles='[Pd+2]',
    functional_groups=(
        stk.SingleAtom(stk.Pd(0, charge=2))
        for i in range(4)
    ),
    position_matrix=[[0., 0., 0.]],
)

ligand = stk.BuildingBlock(
    smiles='NCCN',
    functional_groups=[
        stk.SmartsFunctionalGroupFactory(
            smarts='[#7]~[#6]',
            bonders=(0, ),
            deleters=(),
        ),
    ],
)

metal_complex = stk.ConstructedMolecule(
    topology_graph=stk.metal_complex.CisProtectedSquarePlanar(
        metals=metal_atom,
        ligands=ligand,
    ),
)

Next, we convert the metal complex into a BuildingBlock, taking care to define functional groups which do not have overlapping placer atoms

metal_complex = stk.BuildingBlock.init_from_molecule(
    molecule=metal_complex,
    functional_groups=[
        stk.SmartsFunctionalGroupFactory(
            smarts='[Pd]~[#7]',
            bonders=(0, ),
            deleters=(),
            # The nitrogen atom will be different
            # for each functional group.
            placers=(0, 1),
        ),
    ],
)

We load in the organic linker of the cage as normal

linker = stk.BuildingBlock(
    smiles='C1=NC=CC(C2=CC=NC=C2)=C1',
    functional_groups=[
        stk.SmartsFunctionalGroupFactory(
            smarts='[#6]~[#7X2]~[#6]',
            bonders=(1, ),
            deleters=(),
        ),
    ],
)

And finally, we build the cage with a DativeReactionFactory instance to produce dative bonds.

cage = stk.ConstructedMolecule(
    topology_graph=stk.cage.M4L4Square(
        corners=metal_complex,
        linkers=linker,
        reaction_factory=stk.DativeReactionFactory(
            stk.GenericReactionFactory(
                bond_orders={
                    frozenset({
                        stk.GenericFunctionalGroup,
                        stk.GenericFunctionalGroup,
                    }): 9,
                },
            ),
        ),
    ),
)
Parameters:
  • building_blocks (Iterable[BuildingBlock] | dict[BuildingBlock, tuple[int, ...]]) –

    Can be a iterable of BuildingBlock instances, which should be placed on the topology graph.

    Can also be a dict which maps the BuildingBlock instances to the ids of the vertices it should be placed on. A dict is required when there are multiple building blocks with the same number of functional groups, because in this case the desired placement is ambiguous.

  • vertex_alignments (dict[int, int] | None) – A mapping from the id of a Vertex to an Edge connected to it. The Edge is used to align the first FunctionalGroup of a BuildingBlock placed on that vertex. Only vertices which need to have their default edge changed need to be present in the dict. If None then the default edge is used for each vertex. Changing which Edge is used will mean that the topology graph represents different structural isomers. The edge is referred to by a number between 0 (inclusive) and the number of edges the vertex is connected to (exclusive).

  • vertex_positions (dict[int, ndarray] | None) – A mapping from the id of a Vertex to a custom BuildingBlock position. The default vertex alignment algorithm is still applied. Only vertices which need to have their default position changed need to be present in the dict. Note that any vertices with modified positions will not be scaled like the rest of the building block positions and will not use neighbor placements in its positioning if requested by the default topology. If None then the default placement algorithm is used for each vertex.

  • reaction_factory (ReactionFactory) – The reaction factory to use for creating bonds between building blocks.

  • num_processes (int) – The number of parallel processes to create during construct().

  • optimizer (Optimizer) – Used to optimize the structure of the constructed molecule.

  • scale_multiplier (float) – Scales the positions of the vertices.

Raises:
  • AssertionError – If the any building block does not have a valid number of functional groups.

  • ValueError – If the there are multiple building blocks with the same number of functional_groups in building_blocks, and they are not explicitly assigned to vertices. The desired placement of building blocks is ambiguous in this case.

  • UnoccupiedVertexError – If a vertex of the cage topology graph does not have a building block placed on it.

  • OverlyOccupiedVertexError – If a vertex of the cage topology graph has more than one building block placed on it.

Methods

clone

Return a clone.

construct

Construct a ConstructedMolecule.

get_building_blocks

Yield the building blocks.

get_num_building_block

Get the number of times building_block is present.

get_vertex_alignments

Get the vertex alignments.

with_building_blocks

Return a clone holding different building blocks.

clone()[source]

Return a clone.

Returns:

The clone.

Return type:

Self

construct()

Construct a ConstructedMolecule.

Returns:

The data describing the ConstructedMolecule.

Return type:

ConstructionResult

get_building_blocks()

Yield the building blocks.

Building blocks are yielded in an order based on their position in the topology graph. For two equivalent topology graphs, but with different building blocks, equivalently positioned building blocks will be yielded at the same time.

Yields:

A building block of the topology graph.

Return type:

Iterator[BuildingBlock]

get_num_building_block(building_block)

Get the number of times building_block is present.

Parameters:

building_block (BuildingBlock) – The building block whose frequency in the topology graph is desired.

Returns:

The number of times building_block is present in the topology graph.

Return type:

int

get_vertex_alignments()[source]

Get the vertex alignments.

Returns:

The vertex alignments.

Return type:

dict[int, int]

with_building_blocks(building_block_map)[source]

Return a clone holding different building blocks.

Parameters:

building_block_map (dict[BuildingBlock, BuildingBlock]) – Maps a building block in the current topology graph to the building block which should replace it in the clone. If a building block should be not replaced in the clone, it can be omitted from the map.

Returns:

The clone.

Return type:

Self