Calculators

stk makes extensive use of calculator objects. Calculators are a very general pattern for performing operations on molecules. All calculators are objects with a special method, used to perform the calculation. For example, an optimizer has a special method called optimize(), which optimizes a molecule.

Other types of calculators will have different special methods used to perform calculations on molecules. For example, an EnergyCalculator will define a get_energy() method. This method calculates the energy of a molecule.

All evolutionary algorithm operations are also implemented through calculators, take for example a Mutator such as RandomTopologyGraph

import stk

topology_graphs = [
    stk.cage.EightPlusTwelve(),
    stk.cage.FourPlusSix(),
    stk.cage.TwentyPlusThirty()
]

random_top = RandomTopologyGraph(topology_graphs)

cage = stk.ConstructedMolecule([bb1, bb2], stk.cage.SixPlusNine())

# Perform a mutation.
mutant = random_top.mutate(cage)

A Mutator is a calculator which implements a mutate() method. This method takes a molecule and returns a mutant, i.e. a modified version, of that molecule. In this example, the random_top is a Mutator, which replaces the topology of a molecule with a new one, in order to generate the mutant. In this case, the new topologies would be one of EightPlusTwelve, FourPlusSix or TwentyPlusThirty.

Calculators often support additional options to modify their behaviour. For example, calculators of type Optimizer or EnergyCalculator support caching. This means that if the same molecule and conformer is supplied to the calculator, it will not run the optimization or energy calculation again, it will return the previously calculated value.

Combining Calculators

Calculators can be combined to create complex behaviour on the fly. For example, we may wish to make a 2 step optimization process. First, we perform an optimization using the MMFF force field and the run a MacroModel molecular dynamics conformer search with MacroModelMD. The obvious way to do this is to run the two optimizers in sequence

mmff = stk.MMFF()
macromodel = stk.MacroModelMD('/opt/schrodinger2017-4')
mmff.optimize(mol)
macromodel.optimize(mol)

However, there is a better way! We can use an optimizer called OptimizerSequence. The optimize() method of this optimizer calls the optimize() methods of the optimizers it was initialized with

opt_sequence = OptimizerSequence(mmff, macromodel)
# Optimize with mmff and then with macromodel.
opt_sequence.optimize(mol)

This pattern is quite common and powerful. For example, we can take three different Mutator objects. Each of these defines a different mutate() method. We want to apply one of these mutations at random. We can simply use RandomMutation

random_bb = stk.RandomBuildingBlock(...)
similar_bb = stk.SimilarBulidingBlock(...)
random_topology = stk.RandomTopology(...)

random_mutation = stk.RandomMutation(
    random_bb,
    similar_bb,
    random_topology
)

# Use one of the mutate() methods of random_bb, similar_bb and
# random_topology at random.
mutant1 = random_mutation.mutate(mol)
# The next call use a different mutation to the call above.
mutant2 = random_mutation.mutate(mol)

The RandomMutation.mutate() method randomly selects a Mutator it was initialized with to carry out the mutation on its behalf.

Making New Calculators

New calculators can be added very simply and they can be defined in user code.

A simple example of adding a new calculator in user code.

import stk

class NewEnergyCalculator(stk.EnergyCalculator):
    def get_energy(self, mol):
        return 15

energy_calc = NewEnergyCalculator()
# Calculate the energy with the new calculator.
energy_calc.get_energy(mol)

Note that you can also modify the behaviour of existing calculators in your code, see the Cookbook.

Saving Calculator Results

If you want to save the results which your calculator found you should set use_cache=True and then you can simply use pickle to dump and load the calculator object.