Functional Group

Functional groups define which atoms of a BuildingBlock are modified during ConstructedMolecule construction, and which are used to position it. The class of a FunctionalGroup affects which Reaction can be used with it. See the abstract base class FunctionalGroup for more information.

class FunctionalGroup(atoms, placers, core_atoms)[source]

Bases: object

An abstract base class for functional groups.

It is used to give access to atoms of BuildingBlock molecules which are modified during ConstructedMolecule construction, as well as specify which atoms of the building block should be used for positioning.

Should I use with_ids() or with_atoms() ?

That depends on your use case, however, it is generally better to default to with_ids() unless you need to actually change the atoms held by the functional group. This is because with_ids() preserves the most-derived type of the functional group, while with_atoms() does not. To give an example

import stk

bromo = stk.Bromo(
    bromine=stk.Br(0),
    atom=stk.C(1),
    bonders=(stk.C(1), ),
    deleters=(stk.Br(0), ),
)

bromo2 = bromo.with_ids({
    0: 10,
    1: 100,
})
# bromo2 is still a Bromo functional group.
assert isinstance(bromo2, stk.Bromo)

not_bromo = bromo.with_atoms({
    0: stk.Br(10),
    1: stk.C(100),
})
# not_bromo is not a Bromo functional gorup.
assert not isinstance(not_bromo, stk.Bromo)
# However, it is still an instance of FunctionalGroup,
assert isinstance(not_bromo, stk.FunctionalGroup)
# and of GenericFunctionalGroup
assert isinstance(not_bromo, stk.GenericFunctionalGroup)

The reason that with_atoms() does not produce a Bromo instance is to avoid the following pitfall

pitfall = bromo.with_atoms({
    0: stk.F(10),
    1: stk.C(100),
})
# If with_atoms() returned a Bromo instance then you could
# call get_bromine() on it, but it would hold a F atom!
this_is_a_fluorine = pitfall.get_bromine()

Why would I want to implement a new subclass?

The most common reason you would want to implement a new FunctionalGroup subclass, is because you want to customize the construction of a ConstructedMolecule. Specifically, you want to modify a specific set of atoms in a BuildingBlock when doing construction, and you want to modify them in a specific way. You will usually accompany the creation of the new FunctionalGroup subclass with the creation of a new Reaction subclass, which will perform the custom modification on the atoms held by your new FunctionalGroup subclass. Finally, a new ReactionFactory will also be created, so that your Reaction subclass instances actually get made during construction. Finally, you will pass an instance of your ReactionFactory subclass to the chosen TopologyGraph you want to make, for example Linear, and your custom modification will take place.

See also

functional_group_factory

Used for automated creation of FunctionalGroup instances.

Notes

You might notice that some of the methods of this abstract base class are implemented. This is purely for convenience when implementing subclasses. The implemented public methods are simply default implementations, which can be safely ignored or overridden, when implementing subclasses. Any private methods are implementation details of these default implementations.

Examples

Subclass Implementation

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

Changing the Atoms of a Functional Group

You want to substitute the atoms in the functional group for other atoms. You can do this by using with_atoms() to create a clone of the functional group, which holds the replacement atoms

import stk

c, n, h1, h2 = stk.C(0), stk.N(1), stk.H(2), stk.H(3)
amine = stk.PrimaryAmino(
    nitrogen=n,
    hydrogen1=h1,
    hydrogen2=h2,
    atom=c,
    bonders=(n, ),
    deleters=(h1, h2),
)

n20 = stk.N(20)
h100 = stk.H(100)

# amine_clone is a clone of amine, except that instead of
# holding n, amine_clone holds n20, and instead of holding
# h1  amine_clone holds h100. amine_clone continues to hold
# h2.
amine_clone = amine.with_atoms({
    n.get_id(): n20,
    h1.get_id(): h100,
})

Methods

clone()

Return a clone.

get_atom_ids()

Yield the ids of all atoms in the functional group.

get_atoms()

Yield all the atoms in the functional group.

get_core_atom_ids()

Yield the ids of core atoms held by the functional group.

get_placer_ids()

Yield the ids of placer atoms.

with_atoms(atom_map)

Return a clone holding different atoms.

with_ids(id_map)

Return a clone holding different atom ids.

__init__(atoms, placers, core_atoms)[source]

Initialize a FunctionalGroup.

Parameters
clone()[source]

Return a clone.

Return type

FunctionalGroup

Returns

A clone.

get_atom_ids()[source]

Yield the ids of all atoms in the functional group.

Yields

The id of an Atom.

Return type

Iterator[int]

get_atoms()[source]

Yield all the atoms in the functional group.

Yields

An atom in the functional group.

Return type

Iterator[Atom]

get_core_atom_ids()[source]

Yield the ids of core atoms held by the functional group.

Yields

The id of an Atom.

Return type

Iterator[int]

get_placer_ids()[source]

Yield the ids of placer atoms.

Placer atoms are those, which should be used to calculate the position of the functional group.

Yields

The id of an Atom.

Return type

Iterator[int]

with_atoms(atom_map)[source]

Return a clone holding different atoms.

Parameters

atom_map (dict[int, Atom]) – Maps the id of an atom in the functional group to the new atom the clone should hold. If the id of an atom in the functional group is not found in atom_map, the atom will not be replaced in the clone.

Return type

FunctionalGroup

Returns

The clone.

with_ids(id_map)[source]

Return a clone holding different atom ids.

Parameters

id_map (dict[int, int]) – Maps the id of an atom in the functional group to the new id the clone should hold. If the id of an atom in the functional group is not found in id_map, the atom will not be replaced in the clone.

Return type

FunctionalGroup

Returns

The clone.