lambda.py
lambda.py
#!/bin/env python
# -*- coding: utf-8 -*-
from Sire.IO import *
from Sire.System import *
from Sire.Mol import *
from Sire.MM import *
from Sire.FF import *
from Sire.CAS import *
from Sire.Maths import *
from Sire.Analysis import *
from Sire.System import *
from Sire.Base import *
from Sire.Units import *
from Sire.Squire import *
import Sire.Config
import Sire.Stream
from Sire.Tools.AmberLoader import *
from Sire.Tools import Parameter, resolveParameters
import os
import shutil
import copy
########################################################
# ALL OF THE GLOBAL USER-AVAILABLE QuanToMM PARAMETERS #
########################################################
cutoff_method = Parameter("cutoff method", "shift electrostatics",
"""Method used to apply the non-bonded electrostatic cutoff.""")
rf_dielectric = Parameter("reaction field dielectric", 78.3,
"""Dielectric constant to use if the reaction field cutoff method is used.""")
coul_cutoff = Parameter("coulomb cutoff", 15*angstrom,
"""Coulomb cutoff length""")
lj_cutoff = Parameter("LJ cutoff", 15*angstrom,
"""Lennard Jones cutoff length""")
grid_spacing = Parameter("grid spacing", 1.0*angstrom,
"""Grid spacing used for the grid-based forcefields""")
grid_buffer = Parameter("grid buffer", 2*angstrom,
"""Buffer around the grid used to prevent recalculation
in the grid-based forcefields.""")
disable_grid = Parameter("disable grid", False, """Whether or not to disable use of the grid""")
temperature = Parameter("temperature", 25*celsius, """Simulation temperature""")
pressure = Parameter("pressure", 1*atm, """Simulation pressure. Ignored if a reflection sphere
is used.""")
random_seed = Parameter("random seed", None, """Random number seed. Set this if you
want to have reproducible simulations.""")
lambda_values = Parameter("lambda values", [ 0.0, 0.142, 0.285, 0.429, 0.571, 0.714, 0.857, 1.0 ],
"""The values of lambda to use in the RETI free energy simulation.""")
ligand_name = Parameter("ligand name", "LIG",
"""The name of the ligand. This should be the name of one of the residues
in the ligand, so that this program can find the correct molecule.""")
reflection_radius = Parameter("reflection radius", None,
"""The radius of the reflection sphere""")
topfile = Parameter("topfile", "system.top",
"""Name of the topology file containing the system.""")
crdfile = Parameter("crdfile", "system.crd",
"""Name of the coordinate file containing the coordinates of the
system.""")
s3file = Parameter("s3file", "system.s3",
"""Name to use for the intermediate s3 file that will contain the
system after it has been loaded from the top/crd files.""")
outdir = Parameter("output directory", "output",
"""Name of the directory in which to place all of the output files.""")
restart_file = Parameter("restart file", "quantomm_restart.s3",
"""Name of the restart file to use to save progress during the simulation.""")
sysmoves_file = Parameter("sysmoves file", "quantomm_sysmoves.s3",
"""Name of the file to save the initial QM/MM pre-simulation system.""")
nmoves = Parameter("nmoves", 200, """Number of RETI moves to perform during the simulation.""")
save_pdb = Parameter("save pdb", True,
"""Whether or not to write a PDB of the system after each iteration.""")
save_all_pdbs = Parameter("save all pdbs", False,
"""Whether or not to write all of the PDBs. If not, only PDBs at the two
end points of the simulation will be written.""")
pdb_frequency = Parameter("pdb frequency", 10,
"""The frequency (number of iterations between) saving PDBs""")
restart_frequency = Parameter("restart frequency", 1,
"""The frequency (number of iterations between) saving the restart file for the simulation.""")
nslow = Parameter("nslow", 50,
"""The number of 'slow' moves to perform per RETI iteration.""")
nfast = Parameter("nfast", 1000,
"""The number of 'fast' moves to perform per slow move.""")
scale_charges = Parameter("scale charges", 1.0,
"""The amount by which to scale MM charges in the QM/MM calculation.""")
intermolecular_only = Parameter("intermolecular only", False,
"""Only calculate QM/MM intermolecular energies. This intramolecular energy of the QM atoms
is calculated using the MM forcefield.""")
amberhome = Parameter("amberhome", None,
"""Use this to specify the installation directory of Amber / AmberTools, if you are using
the 'sqm' program. If this is not set, then you must have set this location using
the AMBERHOME environmental variable.""")
qm_program = Parameter("qm program", "sqm",
"""The name of the program to use to calculate QM energies.""")
qm_executable = Parameter("qm executable", None,
"""Exact path to the QM executable used to calculate the QM energies. If
this is not set, then the QM executable will be searched from the path.""")
qm_method = Parameter("qm method", "AM1/d",
"""The string passed to the QM program to specify the QM method to use to model the ligand.""")
basis_set = Parameter("basis set", "VDZ",
"""The string passed to the QM program (molpro, as ab-initio only) to specify the basis set used to model the ligand.""")
qm_zero_energy = Parameter("qm zero energy", None,
"""The value of 'zero' for the QM energy. This is used to shift the QM energy so that it
has the same comparable value as the MM energy. This is normally determined automatically,
but this option allows you to set the value manually.""")
def createQMMMMoves(system):
# pull out all of the molecule groups for the mobile parts of the system
print("Creating the Monte Carlo moves to sample the QM/MM system...")
# create the global set of moves that will be applied to
# the system
moves = WeightedMoves()
# create zmatrix moves to move the protein sidechains
try:
mobile_sidechains = system[MGName("mobile_sidechains")]
if mobile_sidechains.nViews() > 0:
sc_moves = ZMatMove(mobile_sidechains)
moves.add( sc_moves, mobile_sidechains.nViews() )
except:
pass
try:
mobile_backbones = system[MGName("mobile_backbones")]
if mobile_backbones.nViews() > 0:
bb_moves = RigidBodyMC(mobile_backbones)
bb_moves.setCenterOfRotation( GetCOGPoint( AtomName("CA", CaseInsensitive),
AtomName("N", CaseInsensitive) ) )
bb_moves.setMaximumTranslation(0.030*angstrom)
bb_moves.setMaximumRotation(1.0*degrees)
moves.add( bb_moves, mobile_backbones.nViews() )
except:
pass
use_reflection_sphere = False
try:
mobile_ligand = system[MGName("ligand")]
if mobile_ligand.nViews() > 0:
scale_moves = 10
# get the amount to translate and rotate from the ligand's flexibility object
flex = mobile_ligand.moleculeAt(0).molecule().property("flexibility")
# only move the solute if it is not the only molecule in the system
if system.nMolecules() > 1 and (flex.translation().value() != 0 or flex.rotation().value() != 0):
rb_moves = RigidBodyMC(mobile_ligand)
rb_moves.setMaximumTranslation(flex.translation())
rb_moves.setMaximumRotation(flex.rotation())
if system.containsProperty("reflection sphere radius"):
reflection_radius = float(str(system.property("reflection sphere radius"))) * angstroms
reflection_center = system.property("reflection center").toVector()[0]
rb_moves.setReflectionSphere(reflection_center, reflection_radius)
use_reflection_sphere = True
print("Using the reflection sphere to constrain solvent moves...")
scale_moves = scale_moves / 2
moves.add( rb_moves, scale_moves * mobile_ligand.nViews() )
intra_moves = InternalMove(mobile_ligand)
moves.add( intra_moves, scale_moves * mobile_ligand.nViews() )
except:
pass
try:
mobile_solutes = system[MGName("mobile_solutes")]
if mobile_solutes.nViews() > 0:
rb_moves = RigidBodyMC(mobile_solutes)
if system.containsProperty("average solute translation delta"):
translation_delta = float(str(system.property("average solute translation delta")))
else:
translation_delta = 0
if system.containsProperty("average solute rotation delta"):
rotation_delta = float(str(system.property("average solute rotation delta")))
else:
rotation_delta = 0
if translation_delta > 0 and rotation_delta > 0:
rb_moves.setMaximumTranslation(translation_delta * angstroms)
rb_moves.setMaximumRotation(rotation_delta * degrees)
if system.containsProperty("reflection sphere radius"):
reflection_radius = float(str(system.property("reflection sphere radius"))) * angstroms
reflection_center = system.property("reflection center").toVector()[0]
rb_moves.setReflectionSphere(reflection_center, reflection_radius)
moves.add(rb_moves, 4 * mobile_solutes.nViews())
intra_moves = InternalMove(solute_group)
moves.add(intra_moves, 4 * mobile_solutes.nViews())
except:
pass
try:
mobile_solvent = system[MGName("mobile_solvents")]
max_water_translation = 0.15 * angstroms
max_water_rotation = 15 * degrees
if mobile_solvent.nViews() > 0:
rb_moves = RigidBodyMC(mobile_solvent)
rb_moves.setMaximumTranslation(max_water_translation)
rb_moves.setMaximumRotation(max_water_rotation)
if system.containsProperty("reflection sphere radius"):
reflection_radius = float(str(system.property("reflection sphere radius"))) * angstroms
reflection_center = system.property("reflection center").toVector()[0]
rb_moves.setReflectionSphere(reflection_center, reflection_radius)
moves.add(rb_moves, 4 * mobile_solvent.nViews())
except:
pass
moves.setTemperature(temperature.val)
try:
if pressure.val and system.nMolecules() > 1 and system.property("space").isPeriodic():
if not use_reflection_sphere:
print("Running a constant pressure calculation")
all = system[MGName("all")]
volume_move = VolumeMove(all)
volume_move.setTemperature(temperature.val)
volume_move.setPressure(pressure.val)
volume_move.setMaximumVolumeChange( 0.1 * all.nMolecules() * angstrom3 )
moves.add(volume_move, 1)
except:
pass
# Now create a multiple-timestep Monte Carlo move that
# uses the above weighted moves for the fast energy
mtsmc = MTSMC( moves, nfast.val )
mtsmc.setFastEnergyComponent( Symbol("E_{fast}") )
mtsmc.setSlowEnergyComponent( Symbol("E_{slow}") )
seed = random_seed.val
if seed is None:
seed = RanGenerator().randInt(100000,1000000)
print("Using generated random number seed %d" % seed)
else:
print("Using supplied random number seed %d" % seed)
mtsmc.setGenerator( RanGenerator(seed) )
return SameMoves(mtsmc)
def printMoveInfo(moves):
"""Use this function to print out the specific details of the individual moves"""
mtsmc = moves[0]
fast_moves = mtsmc.fastMoves()
for i in range(0, fast_moves.count()):
move = fast_moves[i]
print("%d : %s" % (i, move))
def getMinimumDistance(mol0, mol1):
space = Cartesian()
return space.minimumDistance(CoordGroup(mol0.molecule().property("coordinates").array()), \
CoordGroup(mol1.molecule().property("coordinates").array()))
def setCLJProperties(forcefield, space):
if cutoff_method.val.find("shift electrostatics") != -1:
forcefield.setShiftElectrostatics(True)
elif cutoff_method.val.find("reaction field") != -1:
forcefield.setUseReactionField(True)
forcefield.setReactionFieldDielectric(rf_dielectric.val)
else:
print("Cannot interpret the cutoff method from \"%s\"" % cutoff_method.val, file=sys.stderr)
forcefield.setSpace(space)
forcefield.setSwitchingFunction( HarmonicSwitchingFunction(coul_cutoff.val,coul_cutoff.val,
lj_cutoff.val,lj_cutoff.val) )
return forcefield
def setFakeGridProperties(forcefield, space):
forcefield.setSwitchingFunction( HarmonicSwitchingFunction(coul_cutoff.val,coul_cutoff.val,
lj_cutoff.val,lj_cutoff.val) )
forcefield.setSpace(space)
return forcefield
def setGridProperties(forcefield, extra_buffer=0*angstrom):
forcefield.setGridSpacing(grid_spacing.val)
forcefield.setBuffer(grid_buffer.val + extra_buffer)
forcefield.setLJCutoff(lj_cutoff.val)
forcefield.setCoulombCutoff(coul_cutoff.val)
return forcefield
def setQMProperties(forcefield, space):
forcefield.setSpace(space)
forcefield.setSwitchingFunction( HarmonicSwitchingFunction(coul_cutoff.val,coul_cutoff.val,
lj_cutoff.val,lj_cutoff.val) )
# calculate the total charge on the QM atoms
total_charge = 0.0
for molnum in forcefield.molecules().molNums():
total_charge = forcefield[molnum].evaluate().charge().value()
# round the charge to the nearest integer
print("Charge on QM atoms is %s" % total_charge)
total_charge = round(total_charge)
print("Integer charge on QM atoms is %s" % total_charge)
# Define the QM program used to calculate the QM part of the QM/MM energy
if qm_program.val == "sqm":
qm_prog = SQM()
qm_prog.setMethod(qm_method.val)
qm_prog.setTotalCharge(total_charge)
if amberhome.val is None:
ahome = os.getenv("AMBERHOME")
if ahome is None:
print("ERROR: YOU MUST SET THE AMBERHOME ENVIRONMENTAL VARIABLE!!!")
print("This must point to the installation directory of Amber / AmberTools")
sys.exit(0)
else:
ahome = amberhome.val
if qm_executable.val is None:
qm_prog.setExecutable( findExe("%s/bin/sqm" % ahome).absoluteFilePath() )
else:
qm_prog.setExecutable( findExe(qm_executable.val).absoluteFilePath() )
qm_prog.setEnvironment("AMBERHOME", ahome)
forcefield.setQuantumProgram(qm_prog)
elif qm_program.val == "molpro":
qm_prog = Molpro()
qm_prog.setBasisSet(basis_set.val)
qm_prog.setMethod(qm_method.val)
qm_prog.setTotalCharge(total_charge)
if qm_executable.val is None:
qm_prog.setExecutable( findExe("molpro").absoluteFilePath() )
else:
qm_prog.setExecutable( findExe(qm_executable.val).absoluteFilePath() )
forcefield.setQuantumProgram(qm_prog)
elif qm_program.val is None:
print("WARNING: Using a null (non-existant) QM program!")
qm_prog = NullQM()
forcefield.setQuantumProgram(qm_prog)
else:
print("WARNING: Could not recognise the QM program from %s. Will use the NULL program." \
% qm_program.val)
forcefield.setQuantumProgram(NullQM())
forcefield.setIntermolecularOnly( intermolecular_only.val )
forcefield.setChargeScalingFactor( scale_charges.val )
print("Using QM program %s, MM charges scaled by %s" % (qm_prog, scale_charges.val))
return forcefield
def printEnergies(nrgs):
keys = nrgs.keys()
keys.sort()
for key in keys:
print("%s : %s kcal mol-1" % (key, nrgs[key]))
def loadQMMMSystem():
"""This function is called to set up the system. It sets everything
up, then returns a System object that holds the configured system"""
print("Loading the system...")
t = QTime()
if os.path.exists(s3file.val):
print("Loading existing s3 file %s..." % s3file.val)
loadsys = Sire.Stream.load(s3file.val)
else:
print("Loading from Amber files %s / %s..." % (topfile.val, crdfile.val))
# Add the name of the ligand to the list of solute molecules
sys_scheme = NamingScheme()
sys_scheme.addSoluteResidueName(ligand_name.val)
# Load up the system. This will automatically find the protein, solute, water, solvent
# and ion molecules and assign them to different groups
loadsys = createSystem(topfile.val, crdfile.val, sys_scheme)
ligand_mol = findMolecule(loadsys, ligand_name.val)
if ligand_mol is None:
print("Cannot find the ligand (%s) in the set of loaded molecules!" % ligand_name.val)
sys.exit(-1)
# Center the system with the ligand at (0,0,0)
loadsys = centerSystem(loadsys, ligand_mol)
ligand_mol = loadsys[ligand_mol.number()].molecule()
if reflection_radius.val is None:
loadsys = addFlexibility(loadsys, naming_scheme=sys_scheme )
else:
loadsys = addFlexibility(loadsys, Vector(0), reflection_radius.val, naming_scheme=sys_scheme)
Sire.Stream.save(loadsys, s3file.val)
ligand_mol = findMolecule(loadsys, ligand_name.val)
if ligand_mol is None:
print("Cannot find the ligand (%s) in the set of loaded molecules!" % ligand_name.val)
sys.exit(-1)
# Now build the QM/MM system
system = System("QMMM system")
if loadsys.containsProperty("reflection center"):
reflect_center = loadsys.property("reflection center").toVector()[0]
reflect_radius = float(str(loadsys.property("reflection sphere radius")))
system.setProperty("reflection center", AtomCoords(CoordGroup(1,reflect_center)))
system.setProperty("reflection sphere radius", VariantProperty(reflect_radius))
space = Cartesian()
else:
space = loadsys.property("space")
if loadsys.containsProperty("average solute translation delta"):
system.setProperty("average solute translation delta", \
loadsys.property("average solute translation delta"))
if loadsys.containsProperty("average solute rotation delta"):
system.setProperty("average solute rotation delta", \
loadsys.property("average solute rotation delta"))
# create a molecule group to hold all molecules
all_group = MoleculeGroup("all")
# create a molecule group for the ligand
ligand_group = MoleculeGroup("ligand")
ligand_group.add(ligand_mol)
all_group.add(ligand_mol)
groups = []
groups.append(ligand_group)
# pull out the groups that we want from the two systems
# create a group to hold all of the fixed molecules in the bound leg
fixed_group = MoleculeGroup("fixed_molecules")
if MGName("fixed_molecules") in loadsys.mgNames():
fixed_group.add( loadsys[ MGName("fixed_molecules") ] )
if save_pdb.val:
# write a PDB of the fixed atoms in the bound and free legs
if not os.path.exists(outdir.val):
os.makedirs(outdir.val)
PDB().write(fixed_group, "%s/fixed.pdb" % outdir.val)
# create a group to hold all of the mobile solute molecules
mobile_solutes_group = MoleculeGroup("mobile_solutes")
if MGName("mobile_solutes") in loadsys.mgNames():
mobile_solutes_group.add( loadsys[MGName("mobile_solutes")] )
mobile_solutes_group.remove(ligand_mol)
if mobile_solutes_group.nMolecules() > 0:
all_group.add(mobile_solutes_group)
groups.append(mobile_solutes_group)
# create a group to hold all of the mobile solvent molecules
mobile_solvents_group = MoleculeGroup("mobile_solvents")
if MGName("mobile_solvents") in loadsys.mgNames():
mols = loadsys[MGName("mobile_solvents")]
for molnum in mols.molNums():
solvent_mol = mols[molnum].molecule()
mobile_solvents_group.add(solvent_mol)
all_group.add(mobile_solvents_group)
print("The number of mobile solvent molecules is %d." % mobile_solvents_group.nMolecules())
groups.append(mobile_solvents_group)
# create the groups to hold all of the protein molecules. We will use "extract" to
# pull out only those protein atoms that are in the mobile region
protein_intra_group = MoleculeGroup("protein_intra_group")
mobile_proteins_group = MoleculeGroup("proteins")
mobile_protein_sidechains_group = MoleculeGroup("mobile_sidechains")
mobile_protein_backbones_group = MoleculeGroup("mobile_backbones")
if MGName("protein_sidechains") in loadsys.mgNames() or \
MGName("protein_backbones") in loadsys.mgNames():
all_proteins = Molecules()
try:
protein_sidechains = loadsys[MGName("protein_sidechains")]
all_proteins.add(protein_sidechains.molecules())
except:
protein_sidechains = MoleculeGroup()
try:
protein_backbones = loadsys[MGName("protein_backbones")]
all_proteins.add(protein_backbones.molecules())
except:
protein_backbones = MoleculeGroup()
try:
boundary_molecules = loadsys[MGName("boundary_molecules")]
all_proteins.add(boundary_molecules.molecules())
except:
boundary_molecules = MoleculeGroup()
for molnum in all_proteins.molNums():
protein_mol = all_proteins[molnum].join()
if protein_mol.selectedAll():
protein_intra_group.add(protein_mol)
all_group.add(protein_mol)
mobile_protein = None
try:
mobile_protein = protein_sidechains[molnum]
mobile_protein_sidechains_group.add( mobile_protein )
except:
pass
try:
if mobile_protein is None:
mobile_protein = protein_backbones[molnum]
mobile_protein_backbones_group.add( mobile_protein )
else:
mobile_protein.add( protein_backbones[molnum].selection() )
mobile_protein_backbones_group.add( protein_backbones[molnum] )
except:
pass
if not (mobile_protein is None):
mobile_proteins_group.add( mobile_protein.join() )
else:
# only some of the atoms have been selected. We will extract
# the mobile atoms and will then update all of the other selections
print("Extracting the mobile atoms of protein %s" % protein_mol)
new_protein_mol = protein_mol.extract()
print("Extracted %d mobile atoms from %d total atoms..." % \
(new_protein_mol.nAtoms(), protein_mol.molecule().nAtoms()))
protein_intra_group.add(new_protein_mol)
all_group.add( new_protein_mol )
mobile_protein_view = new_protein_mol.selection()
mobile_protein_view = mobile_protein_view.selectNone()
try:
sidechains = protein_sidechains[molnum]
for i in range(0,sidechains.nViews()):
view = new_protein_mol.selection()
view = view.selectNone()
for atomid in sidechains.viewAt(i).selectedAtoms():
atom = protein_mol.atom(atomid)
resatomid = ResAtomID( atom.residue().number(), atom.name() )
view = view.select( resatomid )
mobile_protein_view = mobile_protein_view.select( resatomid )
if view.nSelected() > 0:
mobile_protein_sidechains_group.add( PartialMolecule(new_protein_mol, view) )
except:
pass
try:
backbones = protein_backbones[molnum]
for i in range(0,backbones.nViews()):
view = new_protein_mol.selection()
view = view.selectNone()
for atomid in backbones.viewAt(i).selectedAtoms():
atom = protein_mol.atom(atomid)
resatomid = ResAtomID( atom.residue().number(), atom.name() )
view = view.select( resatomid )
mobile_protein_view = mobile_protein_view.select( resatomid )
if view.nSelected() > 0:
mobile_protein_backbones_group.add( PartialMolecule(new_protein_mol, view) )
except:
pass
if mobile_protein_view.nSelected() > 0:
mobile_proteins_group.add( PartialMolecule(new_protein_mol, mobile_protein_view) )
groups.append(mobile_protein_backbones_group)
groups.append(mobile_protein_sidechains_group)
groups.append(all_group)
# finished added in all of the proteins
for group in groups:
if group.nMolecules() > 0:
print("Adding group %s" % group.name())
system.add(group)
# now add in the forcefields for the system...
print("Creating the forcefields for the QM/MM system...")
# first, group together the molecules grouped above into convenient
# groups for the forcefields
# group holding just the ligand
ligand_mols = ligand_group.molecules()
# group holding all of the mobile atoms
mobile_mols = mobile_solvents_group.molecules()
mobile_mols.add( mobile_solutes_group.molecules() )
mobile_mols.add( protein_intra_group.molecules() )
# group holding all of the mobile atoms in the bound leg, excluding the
# buffer atoms that are fixed, but bonded to mobile atoms
mobile_buffered_mols = mobile_solvents_group.molecules()
mobile_buffered_mols.add( mobile_solutes_group.molecules() )
mobile_buffered_mols.add( mobile_proteins_group.molecules() )
# group holding all of the protein molecules that need intramolecular terms calculated
protein_intra_mols = protein_intra_group.molecules()
# group holding all of the solute molecules that nede intramolecular terms calculated
solute_intra_mols = mobile_solutes_group.molecules()
forcefields = []
###
### INTRA-ENERGY OF THE LIGAND AND CLUSTER
###
# intramolecular energy of the ligand
ligand_intraclj = IntraCLJFF("ligand:intraclj")
ligand_intraclj = setCLJProperties(ligand_intraclj, space)
ligand_intraclj.add(ligand_mols)
ligand_intraff = InternalFF("ligand:intra")
ligand_intraff.add(ligand_mols)
forcefields.append(ligand_intraclj)
forcefields.append(ligand_intraff)
ligand_mm_nrg = ligand_intraclj.components().total() + ligand_intraff.components().total()
###
### FORCEFIELDS INVOLVING THE LIGAND/CLUSTER AND OTHER ATOMS
###
# forcefield holding the energy between the ligand and the mobile atoms in the
# bound leg
ligand_mobile = InterGroupCLJFF("system:ligand-mobile")
ligand_mobile = setCLJProperties(ligand_mobile, space)
ligand_mobile.add(ligand_mols, MGIdx(0))
ligand_mobile.add(mobile_mols, MGIdx(1))
qm_ligand = QMMMFF("system:ligand-QM")
qm_ligand = setQMProperties(qm_ligand, space)
qm_ligand.add(ligand_mols, MGIdx(0))
zero_energy = 0
if not intermolecular_only.val:
if qm_zero_energy.val is None:
# calculate the delta value for the system - this is the difference between
# the MM and QM intramolecular energy of the ligand
t.start()
print("\nComparing the MM and QM energies of the ligand...")
mm_intra = ligand_intraclj.energy().value() + ligand_intraff.energy().value()
print("MM energy = %s kcal mol-1 (took %s ms)" % (mm_intra, t.elapsed()))
t.start()
qm_intra = qm_ligand.energy().value()
print("QM energy = %s kcal mol-1 (took %s ms)" % (qm_intra, t.elapsed()))
print("\nSetting the QM zero energy to %s kcal mol-1" % (qm_intra - mm_intra))
qm_ligand.setZeroEnergy( (qm_intra-mm_intra) * kcal_per_mol )
zero_energy = qm_intra - mm_intra
else:
print("\nManually setting the QM zero energy to %s" % qm_zero_energy.val)
qm_ligand.setZeroEnergy( qm_zero_energy.val )
zero_energy = qm_zero_energy.val
qm_ligand.add(mobile_mols, MGIdx(1))
ligand_mm_nrg += ligand_mobile.components().total()
ligand_qm_nrg = qm_ligand.components().total() + ligand_mobile.components().lj()
if intermolecular_only.val:
# the QM model still uses the MM intramolecular energy of the ligand
ligand_qm_nrg += ligand_intraclj.components().total() + ligand_intraff.components().total()
forcefields.append(ligand_mobile)
forcefields.append(qm_ligand)
if fixed_group.nMolecules() > 0:
# there are fixed molecules
# Whether or not to disable the grid and calculate all energies atomisticly
if disable_grid:
# we need to renumber all of the fixed molecules so that they don't clash
# with the mobile molecules
print("Renumbering fixed molecules...")
fixed_group = renumberMolecules(fixed_group)
# forcefield holding the energy between the ligand and the fixed atoms in the bound leg
if disable_grid:
ligand_fixed = InterGroupCLJFF("system:ligand-fixed")
ligand_fixed = setCLJProperties(ligand_fixed, space)
ligand_fixed = setFakeGridProperties(ligand_fixed, space)
ligand_fixed.add(ligand_mols, MGIdx(0))
ligand_fixed.add(fixed_group, MGIdx(1))
qm_ligand.add(fixed_group, MGIdx(1))
ligand_mm_nrg += ligand_fixed.components().total()
ligand_qm_nrg += ligand_fixed.components().lj()
forcefields.append(ligand_fixed)
else:
ligand_fixed = GridFF("system:ligand-fixed")
ligand_fixed = setCLJProperties(ligand_fixed, space)
ligand_fixed = setGridProperties(ligand_fixed)
ligand_fixed.add(ligand_mols, MGIdx(0))
ligand_fixed.addFixedAtoms( fixed_group )
qm_ligand.addFixedAtoms( fixed_group )
ligand_mm_nrg += ligand_fixed.components().total()
ligand_qm_nrg += ligand_fixed.components().lj()
forcefields.append(ligand_fixed)
###
### FORCEFIELDS NOT INVOLVING THE LIGAND
###
# forcefield holding the intermolecular energy between all molecules
mobile_mobile = InterCLJFF("mobile-mobile")
mobile_mobile = setCLJProperties(mobile_mobile, space)
mobile_mobile.add(mobile_mols)
other_nrg = mobile_mobile.components().total()
forcefields.append(mobile_mobile)
# forcefield holding the energy between the mobile atoms and
# the fixed atoms
if disable_grid.val:
mobile_fixed = InterGroupCLJFF("mobile-fixed")
mobile_fixed = setCLJProperties(mobile_fixed)
mobile_fixed = setFakeGridProperties(mobile_fixed, space)
mobile_fixed.add(mobile_buffered_mols, MGIdx(0))
mobile_fixed.add(fixed_group, MGIdx(1))
other_nrg += mobile_fixed.components().total()
forcefields.append(mobile_fixed)
else:
mobile_fixed = GridFF("mobile-fixed")
mobile_fixed = setCLJProperties(mobile_fixed, space)
mobile_fixed = setGridProperties(mobile_fixed)
# we use mobile_buffered_group as this group misses out atoms that are bonded
# to fixed atoms (thus preventing large energies caused by incorrect non-bonded calculations)
mobile_fixed.add(mobile_buffered_mols, MGIdx(0))
mobile_fixed.addFixedAtoms(fixed_group)
other_nrg += mobile_fixed.components().total()
forcefields.append(mobile_fixed)
# intramolecular energy of the protein
if protein_intra_mols.nMolecules() > 0:
protein_intraclj = IntraCLJFF("protein_intraclj")
protein_intraclj = setCLJProperties(protein_intraclj, space)
protein_intraff = InternalFF("protein_intra")
for molnum in protein_intra_mols.molNums():
protein_mol = protein_intra_mols[molnum].join()
protein_intraclj.add(protein_mol)
protein_intraff.add(protein_mol)
other_nrg += protein_intraclj.components().total()
other_nrg += protein_intraff.components().total()
forcefields.append(protein_intraclj)
forcefields.append(protein_intraff)
# intramolecular energy of any other solutes
if solute_intra_mols.nMolecules() > 0:
solute_intraclj = IntraCLJFF("solute_intraclj")
solute_intraclj = setCLJProperties(solute_intraclj, space)
solute_intraff = InternalFF("solute_intra")
for molnum in solute_intra_mols.molNums():
solute_mol = solute_intra_mols[molnum].join()
solute_intraclj.add(solute_mol)
solute_intraff.add(solute_mol)
other_nrg += solute_intraclj.components().total()
other_nrg += solute_intraff.components().total()
forcefields.append(solute_intraclj)
forcefields.append(solute_intraff)
###
### NOW ADD THE FORCEFIELDS TO THE SYSTEM
###
###
### SETTING THE FORCEFIELD EXPRESSIONS
###
lam = Symbol("lambda")
e_slow = ((1-lam) * ligand_qm_nrg) + (lam * ligand_mm_nrg) + other_nrg
e_fast = ligand_mm_nrg + other_nrg
de_by_dlam = ligand_mm_nrg - ligand_qm_nrg
for forcefield in forcefields:
system.add(forcefield)
system.setConstant(lam, 0.0)
system.setComponent(Symbol("E_{fast}"), e_fast)
system.setComponent(Symbol("E_{slow}"), e_slow)
system.setComponent(Symbol("dE/dlam"), de_by_dlam)
system.setComponent( system.totalComponent(), e_slow )
system.setProperty("space", space)
if space.isPeriodic():
# ensure that all molecules are wrapped into the space with the ligand at the center
print("Adding in a space wrapper constraint %s, %s" % (space, ligand_mol.evaluate().center()))
system.add( SpaceWrapper( ligand_mol.evaluate().center(), all_group ) )
system.applyConstraints()
print("\nHere are the values of all of the initial energy components...")
t.start()
printEnergies(system.energies())
print("(these took %d ms to evaluate)\n" % t.elapsed())
# Create a monitor to monitor the free energy average
system.add( "dG/dlam", MonitorComponent(Symbol("dE/dlam"), AverageAndStddev()) )
if intermolecular_only.val:
print("\n\n## This simulation uses QM to model *only* the intermolecular energy between")
print("## the QM and MM atoms. The intramolecular energy of the QM atoms is still")
print("## modelled using MM.\n")
else:
print("\n\n## This simulation uses QM to model both the intermolecular and intramolecular")
print("## energies of the QM atoms. Because the this, we have to adjust the 'zero' point")
print("## of the QM potential. You need to add the value %s kcal mol-1 back onto the" % zero_energy)
print("## QM->MM free energy calculated using this program.\n")
return system
def makeRETI(system, moves):
"""This function replicates 'system' over each of the supplied lambda values
and uses 'moves' to sample each of the replicated systems. This uses RETI
to perform replica exchange moves across lambda"""
lam = Symbol("lambda")
replicas = Replicas( len(lambda_values.val) )
replicas.setSubSystem(system)
replicas.setSubMoves(moves)
replicas.setNSubMoves(nslow.val)
replicas.setLambdaComponent(lam)
replicas.setRecordAllStatistics(True)
seed = random_seed.val
if seed is None:
seed = RanGenerator().randInt(100000,1000000)
print("RETI system using generated random number seed %d" % seed)
else:
print("RETI system using supplied random number seed %d" % seed)
replicas.setGenerator( RanGenerator(seed+5) )
for i in range(0, len(lambda_values.val)):
# set the initial lambda value for this replica
replicas.setLambdaValue(i, lambda_values.val[i])
for i in range(0, len(lambda_values.val)):
print(lambda_values.val[i])
print(replicas[i].subSystem().constants())
# Now add monitors for each replica that will copy back
nrgmons = [ "dG/dlam" ]
for nrgmon in nrgmons:
replicas.add( nrgmon, MonitorMonitor(MonitorName(nrgmon), True) )
# now create the replica exchange moves for the replicas
replica_moves = RepExMove()
replica_moves.setDisableSwaps(False)
replica_moves.setGenerator( RanGenerator(seed+7) )
print("\nReturning the QM/MM RETI replicas and moves...")
return (replicas, replica_moves)
def loadQMMM():
"""This loads the QM/MM system"""
system = loadQMMMSystem()
moves = createQMMMMoves(system)
print("\nUsing moves:")
printMoveInfo(moves)
return (system, moves)
def analyseQMMM(replicas, iteration, ti_freenrgs):
"""This function is used to perform all analysis of iteration 'it' of the passed QMMM system"""
print("Analysing iteration %d..." % iteration)
if not os.path.exists(outdir.val):
os.makedirs(outdir.val)
# read the value of delta_lambda from the first system
system = replicas[0].subSystem()
logfile = "%s/results_%0004d.log" % (outdir.val, iteration)
FILE = open(logfile, "w")
print("===========================", file=FILE)
print(" Results for iteration %d" % iteration, file=FILE)
print("===========================", file=FILE)
print("temperature == %f K\n" % replicas[0].subMoves().temperature().to(kelvin), file=FILE)
nreplicas = replicas.nReplicas()
# extract all of the monitors from the replicas
lambda_values = []
dg = {}
write_pdbs = (save_pdb.val) and (iteration % pdb_frequency.val == 0)
if write_pdbs:
print("Saving PDBs of the system at iteration %d..." % iteration)
for i in range(0, nreplicas):
replica = replicas[i]
monitors = replica.monitors()
lamval = replica.lambdaValue()
lambda_values.append(lamval)
if write_pdbs:
if save_all_pdbs.val or (i == 0) or (i == nreplicas-1):
# Save a PDB of the final configuration for the bound and free legs for each lambda value
system = replica.subSystem()
all = system[MGName("all")]
PDB().write(all, "%s/coords_%000006d_%.5f.pdb" % (outdir.val, iteration, lamval))
dg[lamval] = monitors[MonitorName("dG/dlam")][-1].accumulator()
ti_freenrgs.set( iteration, dg )
print("\n=============", file=FILE)
print("Correction free energy for iteration %d equals %s" % (iteration, \
ti_freenrgs[iteration].integrate().values()[-1].y()), file=FILE)
print("==============", file=FILE)
@resolveParameters
def run():
"""This function is used to actually run the simulation according to the
parameters passed in in 'params'"""
t = QTime()
total_t = QTime()
total_t.start()
if os.path.exists(restart_file.val):
t.start()
(qm_system,qm_moves) = Sire.Stream.load(restart_file.val)
print("Loading the restart file took %d ms" % t.elapsed())
else:
# Load the system from the files
t.start()
if os.path.exists(sysmoves_file.val):
(qm_system,qm_moves) = Sire.Stream.load(sysmoves_file.val)
else:
(qm_system,qm_moves) = loadQMMM()
Sire.Stream.save( (qm_system,qm_moves), sysmoves_file.val)
# now replicate the system across all lambda values so that we can
# run a simulation
(qm_system,qm_moves) = makeRETI(qm_system,qm_moves)
Sire.Stream.save( (qm_system,qm_moves), restart_file.val )
print("Initialising the system took %d ms" % t.elapsed())
# see how many blocks of moves we still need to perform...
nattempted = qm_moves.nMoves()
print("Number of iterations to perform: %d. Number of iterations completed: %d." % (nmoves.val, nattempted))
# See if we have any existing free energy statistics files...
t.start()
freenrgs_file = "%s/freenrgs.s3" % outdir.val
if not os.path.exists(freenrgs_file):
ti_freenrgs = TI()
else:
ti_freenrgs = Sire.Stream.load(freenrgs_file)
print("Initialising / loading the free energy files took %d ms" % t.elapsed())
for i in range(nattempted+1, nmoves.val+1):
t.start()
print("Performing iteration %d..." % i)
sim = SupraSim.run( qm_system, qm_moves, 1, True )
sim.wait()
qm_system = sim.system()
qm_moves = sim.moves()
print("...iteration complete (took %d ms)" % t.elapsed())
# write a restart file every N moves in case of crash or run out of time
if i % restart_frequency.val == 0 or i == nmoves.val:
t.start()
print("Saving the restart file from iteration %d..." % i)
# save the old file to a backup
try:
shutil.copy(restart_file.val, "%s.bak" % restart_file.val)
except:
pass
Sire.Stream.save( (qm_system, qm_moves), restart_file.val )
print("...save complete (took %d ms)" % t.elapsed())
t.start()
print("Analysing iteration %d..." % i)
analyseQMMM(qm_system, i, ti_freenrgs)
qm_system.clearAllStatistics()
print("...analysis complete (took %d ms)" % t.elapsed())
if i % restart_frequency.val == 0 or i == nmoves.val:
t.start()
print("Saving the free energy analysis files from iteration %d..." % i)
# save the old file to a backup
try:
shutil.copy(freenrgs_file, "%s.bak" % freenrgs_file)
except:
pass
Sire.Stream.save( ti_freenrgs, freenrgs_file )
print("...save complete (took %d ms)" % t.elapsed())
print("All iterations complete. Total runtime was %d ms" % total_t.elapsed())