Host-guest complexes with OpenMM/OpenFF ======================================= Here we build on the cage optimisation workflow to show how to build host-guest systems using the open-source `OpenMM `_ and `OpenFF `_ infrastructure. This is shown in an example `script `_ that we run through below. .. warning:: OpenMM/OpenFF workflows are more efficient when using GPUs, which is possible with ``stko`` if you have a GPU and if you install the `cuda` option, like so: ``pip install 'stko[cuda]'``. First we load a cage from the previous example, the classic CC3 porous organic cage. And we build to `stk.host_guest.Guest` objects. .. code-block:: python from pathlib import Path import openmm import stk from openff.toolkit import ForceField import stko input_cage = stk.BuildingBlock.init_from_file( Path("cage_openmm_example_directory/opt_cage.mol") ) guest1 = stk.host_guest.Guest( building_block=stk.BuildingBlock("CC"), displacement=(0.0, 3.0, 0.0), ) guest2 = stk.host_guest.Guest( building_block=stk.BuildingBlock("C1CCCC1"), ) hg_complex = stk.ConstructedMolecule( topology_graph=stk.host_guest.Complex( host=stk.BuildingBlock.init_from_molecule(input_cage), guests=(guest1, guest2), optimizer=stk.Spinner(), ), ) .. moldoc:: import moldoc.molecule as molecule import stk try: hg_complex = stk.BuildingBlock.init_from_file( 'source/_static/unopt_hgcomplex.mol', ) except OSError: hg_complex = stk.BuildingBlock.init_from_file( '_static/unopt_hgcomplex.mol', ) moldoc_display_molecule = molecule.Molecule( atoms=( molecule.Atom( atomic_number=atom.get_atomic_number(), position=position, ) for atom, position in zip( hg_complex.get_atoms(), hg_complex.get_position_matrix(), ) ), bonds=( molecule.Bond( atom1_id=bond.get_atom1().get_id(), atom2_id=bond.get_atom2().get_id(), order=bond.get_order(), ) for bond in hg_complex.get_bonds() ), ) Again, we define some settings, this time at lower temperature. .. code-block:: python def integrator( *, temperature: float, friction: float, time_step: float, ) -> openmm.LangevinIntegrator: """Define integrator.""" integrator = openmm.LangevinIntegrator(temperature, friction, time_step) integrator.setRandomNumberSeed(127) return integrator # Settings. force_field = ForceField("openff_unconstrained-2.1.0.offxml") partial_charges = "mmff94" temperature = 300 * openmm.unit.kelvin friction = 10 / openmm.unit.picoseconds time_step = 1 * openmm.unit.femtoseconds We can then run an :class:`stk.OptimizerSequence` built from `OpenMM` classes to get the structure below in a few minutes! .. code-block:: python # Define sequence. optimisation_sequence = stko.OptimizerSequence( # Unrestricted optimisation. stko.OpenMMForceField( # Load the openff-2.1.0 force field appropriate for # vacuum calculations (without constraints) force_field=force_field, restricted=False, partial_charges_method=partial_charges, ), # Molecular dynamics, short for equilibration. stko.OpenMMMD( force_field=force_field, output_directory=output_directory / "md_optimisation", integrator=integrator( temperature=temperature, friction=friction, time_step=time_step, ), random_seed=275, partial_charges_method=partial_charges, # Frequency here is not related to the num confs tested. reporting_freq=100, trajectory_freq=100, # 10 ps num_steps=10_000, num_conformers=10, platform="CUDA", conformer_optimiser=stko.OpenMMForceField( force_field=force_field, restricted=False, partial_charges_method=partial_charges, ), ), # Long MD, for collecting lowest energy conformers. stko.OpenMMMD( force_field=force_field, output_directory=output_directory / "md_optimisation", integrator=integrator( temperature=temperature, friction=friction, time_step=time_step, ), random_seed=275, partial_charges_method=partial_charges, # Frequency here is not related to the num confs tested. reporting_freq=100, trajectory_freq=100, # 0.2 ns num_steps=200_000, # 1 every 4 ps num_conformers=50, platform="CUDA", conformer_optimiser=stko.OpenMMForceField( force_field=force_field, restricted=False, partial_charges_method=partial_charges, ), ), ) optimised_complex = optimisation_sequence.optimize(hg_complex) .. moldoc:: import moldoc.molecule as molecule import stk try: hg_complex = stk.BuildingBlock.init_from_file( 'source/_static/opt_complex.mol', ) except OSError: hg_complex = stk.BuildingBlock.init_from_file( '_static/opt_complex.mol', ) moldoc_display_molecule = molecule.Molecule( atoms=( molecule.Atom( atomic_number=atom.get_atomic_number(), position=position, ) for atom, position in zip( hg_complex.get_atoms(), hg_complex.get_position_matrix(), ) ), bonds=( molecule.Bond( atom1_id=bond.get_atom1().get_id(), atom2_id=bond.get_atom2().get_id(), order=bond.get_order(), ) for bond in hg_complex.get_bonds() ), )