def __init__(self, variables, system, *args, **kwargs): driving_force = openmm.CustomCVForce(_get_energy_function(variables)) for name, value in _get_parameters(variables).items(): driving_force.addGlobalParameter(name, value) for v in variables: driving_force.addCollectiveVariable(v.id, deepcopy(v.force)) for colvar in v.colvars: driving_force.addCollectiveVariable(colvar.id, deepcopy(colvar.force)) np = system.getNumParticles() nb_forces = [f for f in system.getForces() if isinstance(f, (openmm.NonbondedForce, openmm.CustomNonbondedForce))] a, _, _ = system.getDefaultPeriodicBoxVectors() for i, v in enumerate(variables): system.addParticle(v._particle_mass(a.x)) for nb_force in nb_forces: if isinstance(nb_force, openmm.NonbondedForce): nb_force.addParticle(0.0, 1.0, 0.0) else: nb_force.addParticle([0.0]*nb_force.getNumPerParticleParameters()) parameter = driving_force.getCollectiveVariable(2*i) parameter.setParticleParameters(0, np+i, []) system.addForce(driving_force) super().__init__(system, *args, **kwargs) self.setParameter('Lx', a.x) self.variables = variables self.driving_force = driving_force
def _compute_forces(self, ufed, dataframe): collective_variables = [ colvar.id for v in ufed.variables for colvar in v.colvars ] extended_variables = [v.id for v in ufed.variables] all_variables = collective_variables + extended_variables force = openmm.CustomCVForce(_get_energy_function(ufed.variables)) for key, value in _get_parameters(ufed.variables).items(): force.addGlobalParameter(key, value) for variable in all_variables: force.addGlobalParameter(variable, 0) for xv in extended_variables: force.addEnergyParameterDerivative(xv) system = openmm.System() system.addForce(force) system.addParticle(0) platform = openmm.Platform.getPlatformByName('Reference') context = openmm.Context(system, openmm.CustomIntegrator(0), platform) context.setPositions([openmm.Vec3(0, 0, 0)]) n = len(dataframe.index) forces = [np.empty(n) for xv in extended_variables] for j, row in dataframe.iterrows(): for variable in all_variables: context.setParameter(variable, row[variable]) state = context.getState(getParameterDerivatives=True) derivatives = state.getEnergyParameterDerivatives() for i, xv in enumerate(extended_variables): forces[i][j] = -derivatives[xv] return forces
def _interpolation_grid_force(self): self._widths = [] self._bounds = [] self._extra_points = [] for v in self.bias_variables: extra_points = min(self.grid_expansion, v.grid_size) if v.periodic else 0 extra_range = extra_points*v._range/(v.grid_size - 1) self._widths.append(v.grid_size + 2*extra_points) self._bounds += [v.min_value - extra_range, v.max_value + extra_range] self._extra_points.append(extra_points) self._bias = np.zeros(np.prod(self._widths)) num_bias_variables = len(self.bias_variables) if num_bias_variables == 1: self._table = openmm.Continuous1DFunction(self._bias, *self._bounds) elif num_bias_variables == 2: self._table = openmm.Continuous2DFunction(*self._widths, self._bias, *self._bounds) else: self._table = openmm.Continuous3DFunction(*self._widths, self._bias, *self._bounds) expression = f'bias({",".join(v.id for v in self.bias_variables)})' for i, v in enumerate(self.bias_variables): expression += f';{v.id}={v._get_energy_function(i+1)}' force = openmm.CustomCVForce(expression) for i in range(num_bias_variables): x = openmm.CustomExternalForce('x') x.addParticle(0, []) force.addCollectiveVariable(f'x{i+1}', x) force.addTabulatedFunction('bias', self._table) force.addGlobalParameter('Lx', 0) return force
def __init__(self, variables, height, frequency, grid_expansion): self.bias_variables = [cv for cv in variables if cv.sigma is not None] self.height = height self.frequency = frequency self.grid_expansion = grid_expansion self._widths = [] self._bounds = [] self._expanded = [] self._extra_points = [] for cv in self.bias_variables: expanded = cv.periodic # and len(self.bias_variables) > 1 extra_points = min(grid_expansion, cv.grid_size) if expanded else 0 extra_range = extra_points * cv._range / (cv.grid_size - 1) self._widths += [cv.grid_size + 2 * extra_points] self._bounds += [ cv.min_value - extra_range, cv.max_value + extra_range ] self._expanded += [expanded] self._extra_points += [extra_points] self._bias = np.zeros(tuple(reversed(self._widths))) if len(variables) == 1: self._table = openmm.Continuous1DFunction( self._bias.flatten(), *self._bounds, # self.bias_variables[0].periodic, ) elif len(variables) == 2: self._table = openmm.Continuous2DFunction( *self._widths, self._bias.flatten(), *self._bounds, ) elif len(variables) == 3: self._table = openmm.Continuous3DFunction( *self._widths, self._bias.flatten(), *self._bounds, ) else: raise ValueError( 'UFED requires 1, 2, or 3 biased collective variables') parameter_list = ', '.join(f's_{cv.id}' for cv in self.bias_variables) self.force = openmm.CustomCVForce(f'bias({parameter_list})') for cv in self.bias_variables: expression = f'{cv.min_value}+{cv._range}*(x/Lx-floor(x/Lx))' parameter = openmm.CustomExternalForce(expression) parameter.addGlobalParameter('Lx', 0.0) parameter.addParticle(0, []) self.force.addCollectiveVariable(f's_{cv.id}', parameter) self.force.addTabulatedFunction('bias', self._table)
def slab_correction(system): ''' Apply Yeh's long range coulomb correction for slab geometry in z direction to eliminate the undesired interactions between periodic slabs. It's useful for 2-D systems simulated under 3-D periodic condition. For this correction to work correctly: * A vacuum space two times larger than slab thickness is required. * All particles should never diffuse across the z boundaries. * The box size should not change during the simulation. Parameters ---------- system : mm.System The OpenMM system to be simulated Returns ------- force : mm.CustomCVForce ''' muz = mm.CustomExternalForce('q*z') muz.addPerParticleParameter('q') nbforce = [ f for f in system.getForces() if f.__class__ == mm.NonbondedForce ][0] qsum = 0 for i in range(nbforce.getNumParticles()): q = nbforce.getParticleParameters(i)[0].value_in_unit(qe) muz.addParticle(i, [q]) qsum += q if abs(qsum) > 1E-4: raise Exception('Slab correction is not valid for non-neutral system') box = system.getDefaultPeriodicBoxVectors() vol = (box[0][0] * box[1][1] * box[2][2]).value_in_unit(nm**3) # convert from e^2/nm to kJ/mol # 138.93545915168772 _eps0 = CONST.EPS0 * unit.farad / unit.meter # vacuum dielectric constant _conv = (1 / (4 * CONST.PI * _eps0) * qe**2 / nm / unit.item).value_in_unit(kJ_mol) prefactor = 2 * CONST.PI / vol * _conv cvforce = mm.CustomCVForce(f'{prefactor}*muz*muz') cvforce.addCollectiveVariable('muz', muz) system.addForce(cvforce) return cvforce
def __init__(self, variables, temperature, height=None, frequency=None, grid_expansion=20): self.variables = variables self.temperature = _standardize(temperature) self.height = _standardize(height) self.frequency = frequency self.grid_expansion = grid_expansion energy_terms = [] definitions = [] for i, cv in enumerate(self.variables): energy_terms.append( f'0.5*K_{cv.id}*min(d{cv.id},{cv._range}-d{cv.id})^2') definitions.append(f'd{cv.id}=abs({cv.id}-s_{cv.id})') expression = '; '.join([' + '.join(energy_terms)] + definitions) self.driving_force = openmm.CustomCVForce(expression) for i, cv in enumerate(self.variables): self.driving_force.addGlobalParameter(f'K_{cv.id}', cv.force_constant) self.driving_force.addCollectiveVariable(cv.id, cv.openmm_force) expression = f'{cv.min_value}+{cv._range}*(x/Lx-floor(x/Lx))' parameter = openmm.CustomExternalForce(expression) parameter.addGlobalParameter('Lx', 0.0) parameter.addParticle(0, []) self.driving_force.addCollectiveVariable(f's_{cv.id}', parameter) if (all(cv.sigma is None for cv in self.variables) or height is None or frequency is None): self.bias_force = self._metadynamics = None else: self._metadynamics = _Metadynamics( self.variables, self.height, frequency, grid_expansion, ) self.bias_force = self._metadynamics.force
def setup_fah_run(destination_path, protein_pdb_filename, oemol=None, cache=None, restrain_rmsd=False): """ Prepare simulation Parameters ---------- destination_path : str The path to the RUN to be created protein_pdb_filename : str Path to protein PDB file oemol : openeye.oechem.OEMol, optional, default=None The molecule to parameterize, with SDData attached If None, don't include the small molecule restrain_rmsd : bool, optional, default=False If True, restrain RMSD during first equilibration phase """ # Parameters from simtk import unit, openmm protein_forcefield = 'amber14/protein.ff14SB.xml' solvent_forcefield = 'amber14/tip3p.xml' small_molecule_forcefield = 'openff-1.2.0' water_model = 'tip3p' solvent_padding = 10.0 * unit.angstrom ionic_strength = 70 * unit.millimolar # assay buffer: 20 mM HEPES pH 7.3, 1 mM TCEP, 50 mM NaCl, 0.01% Tween-20, 10% glycerol pressure = 1.0 * unit.atmospheres collision_rate = 1.0 / unit.picoseconds temperature = 300.0 * unit.kelvin timestep = 4.0 * unit.femtoseconds iterations = 1000 # 1 ns equilibration nsteps_per_iteration = 250 # Prepare phases import os system_xml_filename = os.path.join(destination_path, 'system.xml.bz2') integrator_xml_filename = os.path.join(destination_path, 'integrator.xml.bz2') state_xml_filename = os.path.join(destination_path, 'state.xml.bz2') # Check if we can skip setup openmm_files_exist = os.path.exists( system_xml_filename) and os.path.exists( state_xml_filename) and os.path.exists(integrator_xml_filename) if openmm_files_exist: return # Create barostat barostat = openmm.MonteCarloBarostat(pressure, temperature) # Create RUN directory if it does not yet exist os.makedirs(destination_path, exist_ok=True) # Load any molecule(s) molecule = None if oemol is not None: from openforcefield.topology import Molecule molecule = Molecule.from_openeye(oemol, allow_undefined_stereo=True) molecule.name = 'MOL' # Ensure residue is MOL print([res for res in molecule.to_topology().to_openmm().residues()]) # Create SystemGenerator import os from simtk.openmm import app forcefield_kwargs = { 'removeCMMotion': False, 'hydrogenMass': 3.0 * unit.amu, 'constraints': app.HBonds, 'rigidWater': True } periodic_kwargs = { 'nonbondedMethod': app.PME, 'ewaldErrorTolerance': 2.5e-04 } forcefields = [protein_forcefield, solvent_forcefield] from openmmforcefields.generators import SystemGenerator openmm_system_generator = SystemGenerator( forcefields=forcefields, molecules=molecule, small_molecule_forcefield=small_molecule_forcefield, cache=cache, barostat=barostat, forcefield_kwargs=forcefield_kwargs, periodic_forcefield_kwargs=periodic_kwargs) # Read protein print(f'Reading protein from {protein_pdb_filename}...') pdbfile = app.PDBFile(protein_pdb_filename) modeller = app.Modeller(pdbfile.topology, pdbfile.positions) if oemol is not None: # Add small molecule to the system modeller.add(molecule.to_topology().to_openmm(), molecule.conformers[0]) # DEBUG : Check residue name with open(os.path.join(destination_path, 'initial-complex.pdb'), 'wt') as outfile: app.PDBFile.writeFile(modeller.topology, modeller.positions, outfile) # Add solvent print('Adding solvent...') kwargs = {'padding': solvent_padding} modeller.addSolvent(openmm_system_generator.forcefield, model='tip3p', ionicStrength=ionic_strength, **kwargs) # Create an OpenMM system print('Creating OpenMM system...') system = openmm_system_generator.create_system(modeller.topology) # Add a virtual bond between protein and ligand to make sure they are not imaged separately if oemol is not None: import mdtraj as md mdtop = md.Topology.from_openmm( modeller.topology) # excludes solvent and ions for res in mdtop.residues: print(res) protein_atom_indices = mdtop.select( '(protein and name CA)') # protein CA atoms ligand_atom_indices = mdtop.select( '((resname MOL) and (mass > 1))') # ligand heavy atoms protein_atom_index = int(protein_atom_indices[0]) ligand_atom_index = int(ligand_atom_indices[0]) force = openmm.CustomBondForce('0') force.addBond(protein_atom_index, ligand_atom_index, []) system.addForce(force) # Add RMSD restraints if requested if restrain_rmsd: print('Adding RMSD restraint...') kB = unit.AVOGADRO_CONSTANT_NA * unit.BOLTZMANN_CONSTANT_kB kT = kB * temperature import mdtraj as md mdtop = md.Topology.from_openmm( pdbfile.topology) # excludes solvent and ions #heavy_atom_indices = mdtop.select('mass > 1') # heavy solute atoms rmsd_atom_indices = mdtop.select( '(protein and (name CA)) or ((resname MOL) and (mass > 1))' ) # CA atoms and ligand heavy atoms rmsd_atom_indices = [int(index) for index in rmsd_atom_indices] custom_cv_force = openmm.CustomCVForce('(K_RMSD/2)*RMSD^2') custom_cv_force.addGlobalParameter('K_RMSD', kT / unit.angstrom**2) rmsd_force = openmm.RMSDForce(modeller.positions, rmsd_atom_indices) custom_cv_force.addCollectiveVariable('RMSD', rmsd_force) force_index = system.addForce(custom_cv_force) # Create OpenM Context platform = openmm.Platform.getPlatformByName('OpenCL') platform.setPropertyDefaultValue('Precision', 'mixed') from openmmtools import integrators integrator = integrators.LangevinIntegrator(temperature, collision_rate, timestep) context = openmm.Context(system, integrator, platform) context.setPositions(modeller.positions) # Report initial potential energy state = context.getState(getEnergy=True) print( f'Initial potential energy is {state.getPotentialEnergy()/unit.kilocalories_per_mole:.3f} kcal/mol' ) # Store snapshots in MDTraj trajectory to examine RMSD import mdtraj as md import numpy as np mdtop = md.Topology.from_openmm(pdbfile.topology) atom_indices = mdtop.select('all') # all solute atoms protein_atom_indices = mdtop.select( 'protein and (mass > 1)') # heavy solute atoms if oemol is not None: ligand_atom_indices = mdtop.select( '(resname MOL) and (mass > 1)') # ligand heavy atoms trajectory = md.Trajectory( np.zeros([iterations + 1, len(atom_indices), 3], np.float32), mdtop) trajectory.xyz[0, :, :] = context.getState(getPositions=True).getPositions( asNumpy=True)[atom_indices] / unit.nanometers # Minimize print('Minimizing...') openmm.LocalEnergyMinimizer.minimize(context) # Equilibrate (with RMSD restraint if needed) import numpy as np from rich.progress import track import time initial_time = time.time() for iteration in track(range(iterations), 'Equilibrating...'): integrator.step(nsteps_per_iteration) trajectory.xyz[iteration + 1, :, :] = context.getState( getPositions=True).getPositions( asNumpy=True)[atom_indices] / unit.nanometers elapsed_time = (time.time() - initial_time) * unit.seconds ns_per_day = (context.getState().getTime() / elapsed_time) / (unit.nanoseconds / unit.day) print(f'Performance: {ns_per_day:8.3f} ns/day') if restrain_rmsd: # Disable RMSD restraint context.setParameter('K_RMSD', 0.0) print('Minimizing...') openmm.LocalEnergyMinimizer.minimize(context) for iteration in track(range(iterations), 'Equilibrating without RMSD restraint...'): integrator.step(nsteps_per_iteration) # Retrieve state state = context.getState(getPositions=True, getVelocities=True, getEnergy=True, getForces=True) system.setDefaultPeriodicBoxVectors(*state.getPeriodicBoxVectors()) modeller.topology.setPeriodicBoxVectors(state.getPeriodicBoxVectors()) print( f'Final potential energy is {state.getPotentialEnergy()/unit.kilocalories_per_mole:.3f} kcal/mol' ) # Equilibrate again if we restrained the RMSD if restrain_rmsd: print('Removing RMSD restraint from system...') system.removeForce(force_index) #if oemol is not None: # # Check final RMSD # print('checking RMSD...') # trajectory.superpose(trajectory, atom_indices=protein_atom_indices) # protein_rmsd = md.rmsd(trajectory, trajectory[-1], atom_indices=protein_atom_indices)[-1] * 10 # Angstroms # oechem.OESetSDData(oemol, 'equil_protein_rmsd', f'{protein_rmsd:.2f} A') # ligand_rmsd = md.rmsd(trajectory, trajectory[-1], atom_indices=ligand_atom_indices)[-1] * 10 # Angstroms # oechem.OESetSDData(oemol, 'equil_ligand_rmsd', f'{ligand_rmsd:.2f} A') # print('RMSD after equilibration: protein {protein_rmsd:8.2f} A | ligand {ligand_rmsd:8.3f} A') # Save as OpenMM print('Exporting for OpenMM FAH simulation...') import bz2 with bz2.open(integrator_xml_filename, 'wt') as f: f.write(openmm.XmlSerializer.serialize(integrator)) with bz2.open(state_xml_filename, 'wt') as f: f.write(openmm.XmlSerializer.serialize(state)) with bz2.open(system_xml_filename, 'wt') as f: f.write(openmm.XmlSerializer.serialize(system)) with bz2.open(os.path.join(destination_path, 'equilibrated-all.pdb.gz'), 'wt') as f: app.PDBFile.writeFile(modeller.topology, state.getPositions(), f) with open(os.path.join(destination_path, 'equilibrated-solute.pdb'), 'wt') as f: import mdtraj mdtraj_topology = mdtraj.Topology.from_openmm(modeller.topology) mdtraj_trajectory = mdtraj.Trajectory( [state.getPositions(asNumpy=True) / unit.nanometers], mdtraj_topology) selection = mdtraj_topology.select('not water') mdtraj_trajectory = mdtraj_trajectory.atom_slice(selection) app.PDBFile.writeFile(mdtraj_trajectory.topology.to_openmm(), mdtraj_trajectory.openmm_positions(0), f) if oemol is not None: # Write molecule as SDF, SMILES, and mol2 for extension in ['sdf', 'mol2', 'smi', 'csv']: filename = os.path.join(destination_path, f'molecule.{extension}') with oechem.oemolostream(filename) as ofs: oechem.OEWriteMolecule(ofs, oemol) # Clean up del context, integrator
pdb = app.PDBFile(pdb_filename) print("Loading forcefield: %s" % ffxml_filenames) forcefield = app.ForceField(*ffxml_filenames) # Create the system print('Creating OpenMM System...') system = forcefield.createSystem(pdb.topology, nonbondedMethod=app.PME, constraints=app.HBonds, removeCMMotion=True) # Add forces print("Adding CV force...") rmsd = openmm.RMSDForce(pdb.positions, list(range(702))) cv_force = openmm.CustomCVForce("rmsd") cv_force.addCollectiveVariable('rmsd', rmsd) bv = mtd.BiasVariable(cv_force, 0, 2.5, 0.1, False) # Add a barostat # print('Adding barostat...') # barostat = openmm.MonteCarloBarostat(pressure, temperature) # system.addForce(barostat) # Create simulation print("Creating Simulation...") integrator = openmm.LangevinIntegrator(temperature, collision_rate, timestep) meta = mtd.Metadynamics(system, [bv], temperature, 3, 1.2 * unit.kilojoules_per_mole, 100) simulation = app.Simulation(pdb.topology, system, integrator) simulation.context.setPositions(pdb.positions)
def prepare_simulation(molecule, basedir, save_openmm=False): """ Prepare simulation systems Parameters ---------- molecule : openeye.oechem.OEMol The molecule to set up basedir : str The base directory for docking/ and fah/ directories save_openmm : bool, optional, default=False If True, save gzipped OpenMM System, State, Integrator """ # Parameters from simtk import unit, openmm water_model = 'tip3p' solvent_padding = 10.0 * unit.angstrom box_size = openmm.vec3.Vec3(3.4,3.4,3.4)*unit.nanometers ionic_strength = 100 * unit.millimolar # 100 pressure = 1.0 * unit.atmospheres collision_rate = 1.0 / unit.picoseconds temperature = 300.0 * unit.kelvin timestep = 4.0 * unit.femtoseconds nsteps_per_iteration = 250 iterations = 10000 # 10 ns (covalent score) protein_forcefield = 'amber14/protein.ff14SB.xml' small_molecule_forcefield = 'openff-1.1.0' #small_molecule_forcefield = 'gaff-2.11' # only if you really like atomtypes solvation_forcefield = 'amber14/tip3p.xml' # Create SystemGenerators import os from simtk.openmm import app from openforcefield.topology import Molecule off_molecule = Molecule.from_openeye(molecule, allow_undefined_stereo=True) print(off_molecule) barostat = openmm.MonteCarloBarostat(pressure, temperature) # docking directory docking_basedir = os.path.join(basedir, 'docking') # gromacs directory gromacs_basedir = os.path.join(basedir, 'gromacs') os.makedirs(gromacs_basedir, exist_ok=True) # openmm directory openmm_basedir = os.path.join(basedir, 'openmm') os.makedirs(openmm_basedir, exist_ok=True) # Cache directory cache = os.path.join(openmm_basedir, f'{molecule.GetTitle()}.json') common_kwargs = {'removeCMMotion': False, 'ewaldErrorTolerance': 5e-04, 'nonbondedMethod': app.PME, 'hydrogenMass': 3.0*unit.amu} unconstrained_kwargs = {'constraints': None, 'rigidWater': False} constrained_kwargs = {'constraints': app.HBonds, 'rigidWater': True} forcefields = [protein_forcefield, solvation_forcefield] from openmmforcefields.generators import SystemGenerator parmed_system_generator = SystemGenerator(forcefields=forcefields, molecules=[off_molecule], small_molecule_forcefield=small_molecule_forcefield, cache=cache, barostat=barostat, forcefield_kwargs={**common_kwargs, **unconstrained_kwargs}) openmm_system_generator = SystemGenerator(forcefields=forcefields, molecules=[off_molecule], small_molecule_forcefield=small_molecule_forcefield, cache=cache, barostat=barostat, forcefield_kwargs={**common_kwargs, **constrained_kwargs}) # Prepare phases import os print(f'Setting up simulation for {molecule.GetTitle()}...') for phase in ['complex', 'ligand']: phase_name = f'{molecule.GetTitle()} - {phase}' print(phase_name) pdb_filename = os.path.join(docking_basedir, phase_name + '.pdb') gro_filename = os.path.join(gromacs_basedir, phase_name + '.gro') top_filename = os.path.join(gromacs_basedir, phase_name + '.top') system_xml_filename = os.path.join(openmm_basedir, phase_name+'.system.xml.gz') integrator_xml_filename = os.path.join(openmm_basedir, phase_name+'.integrator.xml.gz') state_xml_filename = os.path.join(openmm_basedir, phase_name+'.state.xml.gz') # Check if we can skip setup gromacs_files_exist = os.path.exists(gro_filename) and os.path.exists(top_filename) openmm_files_exist = os.path.exists(system_xml_filename) and os.path.exists(state_xml_filename) and os.path.exists(integrator_xml_filename) if gromacs_files_exist and (not save_openmm or openmm_files_exist): continue # Filter out UNK atoms by spruce with open(pdb_filename, 'r') as infile: lines = [ line for line in infile if 'UNK' not in line ] from io import StringIO pdbfile_stringio = StringIO(''.join(lines)) # Read the unsolvated system into an OpenMM Topology pdbfile = app.PDBFile(pdbfile_stringio) topology, positions = pdbfile.topology, pdbfile.positions # Add solvent print('Adding solvent...') modeller = app.Modeller(topology, positions) if phase == 'ligand': kwargs = {'boxSize' : box_size} else: kwargs = {'padding' : solvent_padding} modeller.addSolvent(openmm_system_generator.forcefield, model='tip3p', ionicStrength=ionic_strength, **kwargs) # Create an OpenMM system system = openmm_system_generator.create_system(modeller.topology) # If monitoring covalent distance, add an unused force warheads_found = find_warheads(molecule) covalent = (len(warheads_found) > 0) if covalent and phase=='complex': # Find warhead atom indices sulfur_atom_index = None for atom in topology.atoms(): if (atom.residue.name == 'CYS') and (atom.residue.id == '145') and (atom.name == 'SG'): sulfur_atom_index = atom.index break if sulfur_atom_index is None: raise Exception('CYS145 SG atom cannot be found') print('Adding CustomCVForces...') custom_cv_force = openmm.CustomCVForce('0') for warhead_type, warhead_atom_index in warheads_found.items(): distance_force = openmm.CustomBondForce('r') distance_force.setUsesPeriodicBoundaryConditions(True) distance_force.addBond(sulfur_atom_index, warhead_atom_index, []) custom_cv_force.addCollectiveVariable(warhead_type, distance_force) force_index = system.addForce(custom_cv_force) # Create OpenM Context platform = openmm.Platform.getPlatformByName('CUDA') platform.setPropertyDefaultValue('Precision', 'mixed') integrator = openmm.LangevinIntegrator(temperature, collision_rate, timestep) context = openmm.Context(system, integrator, platform) context.setPositions(modeller.positions) # Report initial potential energy state = context.getState(getEnergy=True) print(f'{molecule.GetTitle()} {phase} : Initial potential energy is {state.getPotentialEnergy()/unit.kilocalories_per_mole:.3f} kcal/mol') # Minimize print('Minimizing...') openmm.LocalEnergyMinimizer.minimize(context) # Equilibrate print('Equilibrating...') from tqdm import tqdm import numpy as np distances = np.zeros([iterations], np.float32) for iteration in tqdm(range(iterations)): integrator.step(nsteps_per_iteration) if covalent and phase=='complex': # Get distance in Angstroms distances[iteration] = min(custom_cv_force.getCollectiveVariableValues(context)[:]) * 10 # Retrieve state state = context.getState(getPositions=True, getVelocities=True, getEnergy=True, getForces=True) system.setDefaultPeriodicBoxVectors(*state.getPeriodicBoxVectors()) modeller.topology.setPeriodicBoxVectors(state.getPeriodicBoxVectors()) print(f'{molecule.GetTitle()} {phase} : Final potential energy is {state.getPotentialEnergy()/unit.kilocalories_per_mole:.3f} kcal/mol') # Remove CustomCVForce if covalent and phase=='complex': print('Removing CustomCVForce...') system.removeForce(force_index) from pymbar.timeseries import detectEquilibration t0, g, Neff = detectEquilibration(distances) distances = distances[t0:] distance_min = distances.min() distance_mean = distances.mean() distance_stddev = distances.std() oechem.OESetSDData(molecule, 'covalent_distance_min', str(distance_min)) oechem.OESetSDData(molecule, 'covalent_distance_mean', str(distance_mean)) oechem.OESetSDData(molecule, 'covalent_distance_stddev', str(distance_stddev)) print(f'Covalent distance: mean {distance_mean:.3f} A : stddev {distance_stddev:.3f} A') # Save as OpenMM if save_openmm: print('Saving as OpenMM...') import gzip with gzip.open(integrator_xml_filename, 'wt') as f: f.write(openmm.XmlSerializer.serialize(integrator)) with gzip.open(state_xml_filename,'wt') as f: f.write(openmm.XmlSerializer.serialize(state)) with gzip.open(system_xml_filename,'wt') as f: f.write(openmm.XmlSerializer.serialize(system)) with gzip.open(os.path.join(openmm_basedir, phase_name+'-explicit.pdb.gz'), 'wt') as f: app.PDBFile.writeFile(modeller.topology, state.getPositions(), f) with gzip.open(os.path.join(openmm_basedir, phase_name+'-solute.pdb.gz'), 'wt') as f: import mdtraj mdtraj_topology = mdtraj.Topology.from_openmm(modeller.topology) mdtraj_trajectory = mdtraj.Trajectory([state.getPositions(asNumpy=True) / unit.nanometers], mdtraj_topology) selection = mdtraj_topology.select('not water') mdtraj_trajectory = mdtraj_trajectory.atom_slice(selection) app.PDBFile.writeFile(mdtraj_trajectory.topology.to_openmm(), mdtraj_trajectory.openmm_positions(0), f) # Convert to gromacs via ParmEd print('Saving as gromacs...') import parmed parmed_system = parmed_system_generator.create_system(modeller.topology) #parmed_system.setDefaultPeriodicBoxVectors(*state.getPeriodicBoxVectors()) structure = parmed.openmm.load_topology(modeller.topology, parmed_system, xyz=state.getPositions(asNumpy=True)) structure.save(gro_filename, overwrite=True) structure.save(top_filename, overwrite=True)
def __init__(self, system, rcutIn, rswitchIn, alchemical_atoms=[], coupling_parameter='lambda', coupling_function='lambda', middle_scale=True, coulomb_scaling=False, lambda_coul=0, use_softcore=False, split_alchemical=True): self.this = copy.deepcopy(system).this Kc = 138.935456637 # Coulomb constant in kJ.nm/mol.e^2 self._parameter = coupling_parameter self._coulomb_scaling = coulomb_scaling self._middle_scale = middle_scale self._use_softcore = use_softcore # Define specific sets of atoms: all_atoms = set(range(self.getNumParticles())) solute_atoms = set(alchemical_atoms) solvent_atoms = all_atoms - solute_atoms # Define force-switched potential expressions: rci = rcutIn.value_in_unit(unit.nanometer) rsi = rswitchIn.value_in_unit(unit.nanometer) fsp = self._force_switched_potential(rci, rsi, Kc) mixing_rules = '; chargeprod = charge1*charge2' mixing_rules += '; sigma = 0.5*(sigma1 + sigma2)' mixing_rules += '; epsilon = sqrt(epsilon1*epsilon2)' # Nonbonded force will only account for solvent-solvent interactions: for force in self.getForces(): if isinstance(force, openmm.NonbondedForce): # Store a copy of the nonbonded force before changes are made: nonbonded = copy.deepcopy(force) # Place it at due group: force.setForceGroup(2 if middle_scale else 1) force.setReciprocalSpaceForceGroup(2 if middle_scale else 1) # Delete all solute interaction parameters: self._solute_charges = {} for i in solute_atoms: charge, _, _ = force.getParticleParameters(i) self._solute_charges[i] = charge force.setParticleParameters(i, 0.0, 1.0, 0.0) # Identify solute-solute exceptions and turn all of them into exclusions: exception_pairs = [] for index in range(force.getNumExceptions()): i, j, _, _, _ = nonbonded.getExceptionParameters(index) if set([i, j]).issubset(solute_atoms): exception_pairs.append(set([i, j])) force.setExceptionParameters(index, i, j, 0.0, 1.0, 0.0) # Identify all other solute-solute interactions. In the system's nonbonded force, # turn them into exclusion exceptions. In the stored copy, turn them into general # exceptions for the sake of forthcoming imports: for i, j in itertools.combinations(solute_atoms, 2): if set([i, j]) not in exception_pairs: force.addException(i, j, 0.0, 1.0, 0.0) q1, sig1, eps1 = nonbonded.getParticleParameters(i) q2, sig2, eps2 = nonbonded.getParticleParameters(j) nonbonded.addException(i, j, q1 * q2, (sig1 + sig2) / 2, np.sqrt(eps1 * eps2)) # Add a force-switched potential with internal cutoff if a RESPA-related # middle scale has been requested: if middle_scale: near_force = openmm.CustomNonbondedForce(fsp + mixing_rules) self._import_from_nonbonded(near_force, force) near_force.setCutoffDistance(rcutIn) near_force.setUseSwitchingFunction(False) near_force.setUseLongRangeCorrection(False) near_force.addGlobalParameter('respa_switch', 0) near_force.setForceGroup(1) self.addForce(near_force) # Because all exceptions in nonbonded become exclusions in near_force, # capture all non-exclusion exceptions into a custom bonded force: exceptions = openmm.CustomBondForce( f'step({rci}-r)*U; U = {fsp}') exceptions.addGlobalParameter('respa_switch', 0) for parameter in ['chargeprod', 'sigma', 'epsilon']: exceptions.addPerBondParameter(parameter) for index in range(force.getNumExceptions()): i, j, chargeprod, sigma, epsilon = force.getExceptionParameters( index) if chargeprod / chargeprod.unit != 0.0 or epsilon / epsilon.unit != 0.0: exceptions.addBond(i, j, (chargeprod, sigma, epsilon)) if exceptions.getNumBonds() > 0: exceptions.setForceGroup(1) self.addForce(exceptions) # Store a pointer to the altered non-bonded force: self._nonbonded_force = force else: # Place bonded and other forces at group 0: force.setForceGroup(0) # Return if no solute atoms were specified: if not solute_atoms: return # To allow decoupling rather than annihilation, solute-solute interactions are handled # by a custom bond force without a cut-off: ljc = f'4*epsilon*x*(x - 1) + {Kc}*chargeprod/r; x = (sigma/r)^6' full_range = openmm.CustomBondForce(ljc) full_range.setForceGroup(2 if middle_scale else 1) intrasolute_forces = [full_range] # If a RESPA-related middle scale has been requested, also create a short-ranged version: if middle_scale: short_range = openmm.CustomBondForce(f'step({rci}-r)*U; U = {fsp}') short_range.addGlobalParameter('respa_switch', 0) short_range.setForceGroup(1) intrasolute_forces.append(short_range) for force in intrasolute_forces: for parameter in ['chargeprod', 'sigma', 'epsilon']: force.addPerBondParameter(parameter) self.addForce(force) # Add interactions due to solute-solute pairs previously treated as exceptions: for index in range(nonbonded.getNumExceptions()): i, j, chargeprod, sigma, epsilon = nonbonded.getExceptionParameters( index) if set([i, j]).issubset(solute_atoms): for force in intrasolute_forces: force.addBond(i, j, (chargeprod, sigma, epsilon)) # NOTE: if Coulomb scaling treatment was requested, the electrostatic part of full-ranged # solute-solvent interactions will be enabled by reactivating solute charges while keeping # all intra-solute interactions excluded. # If both Coulomb scaling and a RESPA-related middle scale were requested, the electrostatic # part of short-ranged solute-solvent interactions must be added as well: if coulomb_scaling and middle_scale: # Create a force-switched electrostatic potential and add it to the system: fsep = self._force_switched_eletrostatic_potential(rci, rsi, Kc) short_range = openmm.CustomNonbondedForce(fsep + mixing_rules) self._import_from_nonbonded(short_range, nonbonded) short_range.setCutoffDistance(rcutIn) short_range.setUseSwitchingFunction(False) short_range.setUseLongRangeCorrection(False) short_range.addGlobalParameter('respa_switch', 0) short_range.setForceGroup(1) short_range.addInteractionGroup(solute_atoms, solvent_atoms) self.addForce(short_range) self._fsep_force = short_range if use_softcore: # Softcore potential is fully considered in the middle time scale: ljsoft = f'4*{coupling_parameter}*epsilon*x*(x - 1)' ljsoft += f'; x = 1/((r/sigma)^6 + 0.5*(1-{coupling_parameter}))' full_range = openmm.CustomNonbondedForce(ljsoft + mixing_rules) self._import_from_nonbonded(full_range, nonbonded, import_globals=True) full_range.addInteractionGroup(solute_atoms, solvent_atoms) full_range.addGlobalParameter(coupling_parameter, 1.0) full_range.addEnergyParameterDerivative(coupling_parameter) full_range.setForceGroup(2 if middle_scale else 1) self.addForce(full_range) # Store force object related to alchemical coupling/decoupling: self._alchemical_vdw_force = full_range if middle_scale: # In the current version, the full softcore potential is allocated in the middle # time scale because it would be difficult to apply the force-switch strategy. This # might be reviewed in the future. short_range = copy.deepcopy(full_range) short_range.setEnergyFunction(f'respa_switch*{ljsoft}' + mixing_rules) short_range.addGlobalParameter('respa_switch', 0) short_range.setForceGroup(1) self.addForce(short_range) else: # The van der Waals part of solute-solvent interactions are defined as collective # variables multiplied by a coupling function: potential = '((gt0-gt1)*S + gt1)*alchemical_vdw_energy' potential += f'; gt0 = step({coupling_parameter})' potential += f'; gt1 = step({coupling_parameter}-1)' potential += f'; S = {coupling_function}' cv_force = openmm.CustomCVForce(potential) cv_force.addGlobalParameter(coupling_parameter, 1.0) cv_force.addEnergyParameterDerivative(coupling_parameter) # For the van der Waals part of solute-solvent interactions, it is considered that no # exceptions exist which involve a solute atom and a solvent atom (this might be # reviewed in future versions): lj = f'4*epsilon*x*(x - 1); x = (sigma/r)^6' full_range = openmm.CustomNonbondedForce(lj + mixing_rules) self._import_from_nonbonded(full_range, nonbonded, import_globals=True) full_range.addInteractionGroup(solute_atoms, solvent_atoms) full_range_cv_force = copy.deepcopy(cv_force) full_range_cv_force.addCollectiveVariable('alchemical_vdw_energy', full_range) full_range_cv_force.setForceGroup(2 if middle_scale else 1) self.addForce(full_range_cv_force) # Store force object related to alchemical coupling/decoupling: self._alchemical_vdw_force = full_range_cv_force if middle_scale and split_alchemical: fsljp = self._force_switched_potential(rci, rsi, 0.0) short_range = openmm.CustomNonbondedForce(fsljp + mixing_rules) self._import_from_nonbonded(short_range, nonbonded) short_range.setCutoffDistance(rcutIn) short_range.setUseSwitchingFunction(False) short_range.setUseLongRangeCorrection(False) short_range.addGlobalParameter('respa_switch', 0) short_range.addInteractionGroup(solute_atoms, solvent_atoms) short_range_cv_force = cv_force short_range_cv_force.addCollectiveVariable( 'alchemical_vdw_energy', short_range) short_range_cv_force.setForceGroup(1) self.addForce(short_range_cv_force) elif middle_scale: short_range = copy.deepcopy(full_range) short_range.setEnergyFunction(f'respa_switch*{lj}' + mixing_rules) short_range.addGlobalParameter('respa_switch', 0) short_range.setForceGroup(1) self.addForce(short_range) # Store Coulomb scaling constant as zero, but reset it if a different value has been passed: self._lambda_coul = 0 self.reset_coulomb_scaling_factor(lambda_coul)
def restrain_particle_number(system, particles, direction, bound, sigma, target, k, weights=None): ''' Restrain the number of selected particles in a region. The region is defined by direction (x, y or z) and bound (lower and upper). Each particle is consider as a Gaussian distribution with standard deviation equal to sigma. The number of particles is restrained to the target value using a harmonic function with force constant k. Parameters ---------- system : mm.System particles : list of int direction : ['x', 'y', 'z'] bound : list of float sigma : float Variance of the particle Gaussian in unit of nm target : float k : float Strength of the harmonic restraint in unit of kJ/mol weights : list of float, optional Returns ------- force : mm.CustomCVForce ''' if direction not in ['x', 'y', 'z']: raise Exception('direction can only be x, y or z') _min, _max = bound if unit.is_quantity(_min): _min = _min.value_in_unit(nm) if unit.is_quantity(_max): _max = _max.value_in_unit(nm) if unit.is_quantity(sigma): sigma = sigma.value_in_unit(nm) if unit.is_quantity(k): k = k.value_in_unit(kJ_mol) if weights is None: weights = [1.0] * len(particles) if len(weights) != len(particles): raise Exception('particles and weights should have the same length') if _min is not None: str_min = f'erf(({_min}-{direction})/{2 ** 0.5 * sigma})' else: str_min = '-1' if _max is not None: str_max = f'erf(({_max}-{direction})/{2 ** 0.5 * sigma})' else: str_max = '1' nforce = mm.CustomExternalForce(f'0.5*({str_max}-{str_min})*weight') nforce.addPerParticleParameter('weight') for i, w in zip(particles, weights): nforce.addParticle(i, [w]) cvforce = mm.CustomCVForce(f'0.5*{k}*(number-{target})^2') cvforce.addCollectiveVariable('number', nforce) system.addForce(cvforce) return cvforce
0.208691 ]) weights2 = list([ -0.002846, 0.023837, 0.535975, 0.777245, 0.040945, 0.1803, -0.247547, 0.112163 ]) for n in range(8): w1[sel_feat[n]] = weights1[n] w2[sel_feat[n]] = weights2[n] # Specify a unique CustomCVForce expression_1 = str() for i in range(len(sel_feat) - 1): expression_1 += f'({w1[sel_feat[i]]} * feat_{i}) + ' expression_1 += f'({w1[sel_feat[i+1]]} * feat_{i+1})' cv_force_1 = mm.CustomCVForce(expression_1) for i in range(len(sel_feat)): print(w1[sel_feat[i]]) print(feat_dict[sel_feat[i] - 1]) cv_force_1.addCollectiveVariable(f'feat_{i}', feat_dict[sel_feat[i] - 1]) bv_1 = metadynamics.BiasVariable(cv_force_1, cv_min_1, cv_max_1, cv_std_1, periodic=False) expression_2 = str() for i in range(len(sel_feat) - 1): expression_2 += f'({w2[sel_feat[i]]} * feat_{i}) + ' expression_2 += f'({w2[sel_feat[i+1]]} * feat_{i+1})' cv_force_2 = copy.deepcopy(cv_force_1)
# distances dis_0 = mm.CustomBondForce("r") dis_0.addBond(int(dis[0][0]), int(dis[0][1])) dis_1 = mm.CustomBondForce("r") dis_1.addBond(int(dis[1][0]), int(dis[1][1])) dis_2 = mm.CustomBondForce("r") dis_2.addBond(int(dis[2][0]), int(dis[2][1])) dis_3 = mm.CustomBondForce("r") dis_3.addBond(int(dis[3][0]), int(dis[3][1])) dis_4 = mm.CustomBondForce("r") dis_4.addBond(int(dis[4][0]), int(dis[4][1])) print("Done populating dihedrals and distances.") # Specify a unique CustomCVForce cv_force = mm.CustomCVForce( 'dih_0 + dih_1 + dih_2 + dih_3 + dih_4 + dih_5 + dih_6 + dih_7 + dis_0 + dis_1 + dis_2 + dis_3 + dis_4' ) cv_force.addCollectiveVariable('dih_0', dih_0) cv_force.addCollectiveVariable('dih_1', dih_1) cv_force.addCollectiveVariable('dih_2', dih_2) cv_force.addCollectiveVariable('dih_3', dih_3) cv_force.addCollectiveVariable('dih_4', dih_4) cv_force.addCollectiveVariable('dih_5', dih_5) cv_force.addCollectiveVariable('dih_6', dih_6) cv_force.addCollectiveVariable('dih_7', dih_7) cv_force.addCollectiveVariable('dis_0', dis_0) cv_force.addCollectiveVariable('dis_1', dis_1) cv_force.addCollectiveVariable('dis_2', dis_2) cv_force.addCollectiveVariable('dis_3', dis_3) cv_force.addCollectiveVariable('dis_4', dis_4) bv = mtd.BiasVariable(cv_force, -np.pi * 8, np.pi * 8 + 10 * 5, 0.5, True)
def _auto_create_thermodynamic_states(self, structure, topology, reference_thermodynamic_state, spacing=0.25 * unit.angstroms): """ Create thermodynamic states for sampling along pore axis. """ topo = md.Topology.from_openmm(topology) data = DataPoints(structure, topo) data.writeCoordinates(open('cylinder.xyz', 'w')) cylinder = CylinderFitting(data.coordinates) print(cylinder.vmdCommands(), file=open('vmd_commands.txt', 'w')) b_index, t_index = cylinder.atomsInExtremes(data.coordinates, n=5) cylinder.writeExtremesCoords(data.coordinates, b_index, t_index, open('extremes.xyz', 'w')) bottom_atoms = [] top_atoms = [] for idx in b_index: bottom_atoms.append(data.index[idx]) for idx in t_index: top_atoms.append(data.index[idx]) axis_distance = cylinder.height * unit.angstroms selection = '(residue {}) and (mass > 1.5)'.format(self.ligand_resseq) print('Determining ligand atoms using "{}"...'.format(selection)) ligand_atoms = self.mdtraj_topology.select(selection) expansion_factor = 1.3 nstates = int(expansion_factor * axis_distance / spacing) + 1 print('nstates: {}'.format(nstates)) sigma_y = axis_distance / float( nstates) # stddev of force-free fluctuations in y-axis K_y = self.kT / (sigma_y**2) # spring constant print('vertical sigma_y = {:.3f} A'.format(sigma_y / unit.angstroms)) # Compute restraint width scale_factor = 0.25 sigma_xz = scale_factor * cylinder.r * unit.angstroms # stddev of force-free fluctuations in xz-plane K_xz = self.kT / (sigma_xz**2) # spring constant Kmax = K_y Kmin = K_xz dr = axis_distance * (expansion_factor - 1.0) / 2.0 rmax = axis_distance + dr rmin = -dr # Create restraint state that encodes this axis print('Creating restraint...') from yank.restraints import RestraintState # Collective variable definitions common = 'r = distance(g1,g2);' common += 'theta = angle(g1,g2,g3);' r_parallel = openmm.CustomCentroidBondForce(3, 'r*cos(theta);' + common) r_orthogonal = openmm.CustomCentroidBondForce(3, 'r*sin(theta);' + common) for cv in [r_parallel, r_orthogonal]: cv.addGroup([int(index) for index in ligand_atoms]) cv.addGroup([int(index) for index in bottom_atoms]) cv.addGroup([int(index) for index in top_atoms]) cv.addBond([0, 1, 2], []) energy_parallel = '(K_parallel/2)*(r_parallel-r0)^2;' energy_parallel += 'r0 = lambda_restraints * (rmax - rmin) + rmin;' self.cvforce_parallel = openmm.CustomCVForce(energy_parallel) self.cvforce_parallel.addCollectiveVariable('r_parallel', r_parallel) energy_orthogonal = '(1/2)*(Kmin + S*(Kmax - Kmin))*(r_orthogonal^2);' energy_orthogonal += 'S = 1 - step(z_ext-z)*(1 + u^3*(15*u - 6*u^2 - 10));' energy_orthogonal += 'u = step(z-z_int)*(z - z_int)/(z_ext - z_int);' energy_orthogonal += 'z = abs(r0-z_c);' energy_orthogonal += 'r0 = lambda_restraints * (rmax - rmin) + rmin;' self.cvforce_orthogonal = openmm.CustomCVForce(energy_orthogonal) self.cvforce_orthogonal.addCollectiveVariable('r_orthogonal', r_orthogonal) for force in [self.cvforce_parallel, self.cvforce_orthogonal]: force.addGlobalParameter('rmax', rmax) force.addGlobalParameter('rmin', rmin) force.addGlobalParameter('lambda_restraints', 1.0) self.cvforce_parallel.addGlobalParameter('K_parallel', K_y) self.cvforce_orthogonal.addGlobalParameter('Kmax', Kmax) self.cvforce_orthogonal.addGlobalParameter('Kmin', Kmin) self.cvforce_orthogonal.addGlobalParameter('z_c', axis_distance / 2.0) self.cvforce_orthogonal.addGlobalParameter('z_int', 0.8 * (axis_distance / 2.0)) self.cvforce_orthogonal.addGlobalParameter('z_ext', 1.3 * (axis_distance / 2.0)) self.system.addForce(self.cvforce_parallel) self.system.addForce(self.cvforce_orthogonal) # Update reference thermodynamic state print('Updating system in reference thermodynamic state...') self.reference_thermodynamic_state.set_system(self.system, fix_state=True) ## Create alchemical state ##from openmmtools.alchemy import AlchemicalState ##alchemical_state = AlchemicalState.from_system(self.reference_thermodynamic_state.system) # Create restraint state restraint_state = RestraintState(lambda_restraints=1.0) # Create thermodynamic states to be sampled # TODO: Should we include an unbiased state? initial_time = time.time() thermodynamic_states = list() ##compound_state = states.CompoundThermodynamicState(self.reference_thermodynamic_state, composable_states=[alchemical_state, restraint_state]) compound_state = states.CompoundThermodynamicState( self.reference_thermodynamic_state, composable_states=[restraint_state]) for lambda_restraints in np.linspace(0, 1, nstates): thermodynamic_state = copy.deepcopy(compound_state) thermodynamic_state.lambda_restraints = lambda_restraints # #thermodynamic_state.lambda_sterics = 1.0 # #thermodynamic_state.lambda_electrostatics = 1.0 thermodynamic_states.append(thermodynamic_state) elapsed_time = time.time() - initial_time print('Creating thermodynamic states took %.3f s' % elapsed_time) return thermodynamic_states
def __init__(self, system, variables, temperature, biasFactor, height, frequency, saveFrequency=None, biasDir=None): """Create a Metadynamics object. Parameters ---------- system: System the System to simulate. A CustomCVForce implementing the bias is created and added to the System. variables: list of BiasVariables the collective variables to sample temperature: temperature the temperature at which the simulation is being run. This is used in computing the free energy. biasFactor: float used in scaling the height of the Gaussians added to the bias. The collective variables are sampled as if the effective temperature of the simulation were temperature*biasFactor. height: energy the initial height of the Gaussians to add frequency: int the interval in time steps at which Gaussians should be added to the bias potential saveFrequency: int (optional) the interval in time steps at which to write out the current biases to disk. At the same time it writes biases, it also checks for updated biases written by other processes and loads them in. This must be a multiple of frequency. biasDir: str (optional) the directory to which biases should be written, and from which biases written by other processes should be loaded """ if not unit.is_quantity(temperature): temperature = temperature * unit.kelvin if not unit.is_quantity(height): height = height * unit.kilojoules_per_mole if biasFactor < 1.0: raise ValueError('biasFactor must be >= 1') if (saveFrequency is None and biasDir is not None) or (saveFrequency is not None and biasDir is None): raise ValueError('Must specify both saveFrequency and biasDir') if saveFrequency is not None and (saveFrequency < frequency or saveFrequency % frequency != 0): raise ValueError('saveFrequency must be a multiple of frequency') self.variables = variables self.temperature = temperature self.biasFactor = biasFactor self.height = height self.frequency = frequency self.biasDir = biasDir self.saveFrequency = saveFrequency self._id = np.random.randint(0x7FFFFFFF) self._saveIndex = 0 self._selfBias = np.zeros(tuple(v.gridWidth for v in variables)) self._totalBias = np.zeros(tuple(v.gridWidth for v in variables)) self._loadedBiases = {} self._deltaT = temperature * (biasFactor - 1) varNames = ['cv%d' % i for i in range(len(variables))] self._force = mm.CustomCVForce('table(%s)' % ', '.join(varNames)) for name, var in zip(varNames, variables): self._force.addCollectiveVariable(name, var.force) widths = [v.gridWidth for v in variables] mins = [v.minValue for v in variables] maxs = [v.maxValue for v in variables] if len(variables) == 1: self._table = mm.Continuous1DFunction(self._totalBias.flatten(), mins[0], maxs[0]) elif len(variables) == 2: self._table = mm.Continuous2DFunction(widths[0], widths[1], self._totalBias.flatten(), mins[0], maxs[0], mins[1], maxs[1]) elif len(variables) == 3: self._table = mm.Continuous3DFunction(widths[0], widths[1], widths[2], self._totalBias.flatten(), mins[0], maxs[0], mins[1], maxs[1], mins[2], maxs[2]) else: raise ValueError( 'Metadynamics requires 1, 2, or 3 collective variables') self._force.addTabulatedFunction('table', self._table) self._force.setForceGroup(31) system.addForce(self._force) self._syncWithDisk()
def _anneal_ligand(self, structure, index): """ Anneal ligand interactions to clean up clashes. Returns ------- positions : unit.Quantity Positions of all atoms after annealing the ligand """ reference_system = copy.deepcopy(self.phases[index].system) protein = self.phases[index].mdtraj_top.select( f'protein and backbone and type CA').tolist() rmsd = openmm.RMSDForce( self.phases[index].structure.positions, protein + self.phases[index].lg1_idx + self.phases[index].lg2_idx) energy_expression = 'step(dRMSD) * (K_RMSD/2)*dRMSD^2; dRMSD = (RMSD-RMSD0);' restraint_force = openmm.CustomCVForce(energy_expression) restraint_force.addCollectiveVariable('RMSD', rmsd) restraint_force.addGlobalParameter( 'K_RMSD', 2 * unit.kilocalories_per_mole / (unit.angstroms**2)) restraint_force.addGlobalParameter('RMSD0', 2 * unit.angstroms) reference_system.addForce(restraint_force) alchemical_system = self._alchemically_modify_ligand( reference_system, index) from openmmtools.alchemy import AlchemicalState alchemical_state_zero = mmtools.alchemy.AlchemicalState.from_system( alchemical_system, parameters_name_suffix='zero') alchemical_state_one = mmtools.alchemy.AlchemicalState.from_system( alchemical_system, parameters_name_suffix='one') thermodynamic_state = states.ThermodynamicState( system=alchemical_system, temperature=300 * unit.kelvin) composable_states = [alchemical_state_zero, alchemical_state_one] compound_states = states.CompoundThermodynamicState( thermodynamic_state, composable_states=composable_states) sampler_state = states.SamplerState( positions=structure.positions, box_vectors=structure.topology.getPeriodicBoxVectors()) # Anneal n_annealing_steps = 1000 integrator = openmm.LangevinIntegrator(300 * unit.kelvin, 90.0 / unit.picoseconds, 1.0 * unit.femtoseconds) context, integrator = mmtools.cache.global_context_cache.get_context( compound_states, integrator) sampler_state.apply_to_context(context) compound_states.lambda_sterics_one = 0.0 compound_states.lambda_electrostatics_one = 0.0 compound_states.apply_to_context(context) print('Annealing sterics of ligand 1...') for step in progressbar.progressbar(range(n_annealing_steps)): compound_states.lambda_sterics_zero = float(step) / float( n_annealing_steps) compound_states.lambda_electrostatics_zero = 0.0 compound_states.apply_to_context(context) integrator.step(1) print('Annealing electrostatics of ligand 1...') for step in progressbar.progressbar(range(n_annealing_steps)): compound_states.lambda_sterics_zero = 1.0 compound_states.lambda_electrostatics_zero = float(step) / float( n_annealing_steps) compound_states.apply_to_context(context) integrator.step(1) compound_states.lambda_sterics_zero = 1.0 compound_states.lambda_electrostatics_zero = 1.0 compound_states.apply_to_context(context) print('Annealing sterics of ligand 2...') for step in progressbar.progressbar(range(n_annealing_steps)): compound_states.lambda_sterics_one = float(step) / float( n_annealing_steps) compound_states.lambda_electrostatics_one = 0.0 compound_states.apply_to_context(context) integrator.step(1) print('Annealing electrostatics of ligand 2...') for step in progressbar.progressbar(range(n_annealing_steps)): compound_states.lambda_sterics_one = 1.0 compound_states.lambda_electrostatics_one = float(step) / float( n_annealing_steps) compound_states.apply_to_context(context) integrator.step(1) compound_states.apply_to_context(context) sampler_state.update_from_context(context) # Compute the final energy of the system. final_energy = thermodynamic_state.reduced_potential(context) print('final alchemical energy {:8.3f}kT'.format(final_energy)) return sampler_state.positions
quintent_vol.addPerBondParameter('alpha1') quintent_vol.addPerBondParameter('alpha2') quintent_vol.addPerBondParameter('alpha3') quintent_vol.addPerBondParameter('alpha4') quintent_vol.addPerBondParameter('alpha5') quintent_vol.addGlobalParameter('height', height) for q in quintents: quintent_vol.addBond( [q[0], q[1], q[2], q[3], q[4]], [alpha[q[0]], alpha[q[1]], alpha[q[2]], alpha[q[3]], alpha[q[4]]]) vol0 = 0.5301799 * unit.nanometer**3 K = 50000 * unit.kilojoules_per_mole / unit.nanometer**6 energy = '(K/2)*((p + t + q + qui) - vol0)^2;' cvforce = openmm.CustomCVForce(energy) cvforce.addCollectiveVariable('p', pairs_vol) cvforce.addCollectiveVariable('t', triplets_vol) cvforce.addCollectiveVariable('q', quad_vol) cvforce.addCollectiveVariable('qui', quintent_vol) cvforce.addGlobalParameter('K', K) cvforce.addGlobalParameter('vol0', vol0) cvforce.setForceGroup(29) system.addForce(cvforce) K_c = 200 * unit.kilojoules_per_mole / unit.angstroms**2 force = openmm.CustomCentroidBondForce(2, '(K_c/2)*distance(g1, g2)^2') force.addGlobalParameter('K_c', K_c) force.addGroup([int(index) for index in lig1_heavy_atoms]) force.addGroup([int(index) for index in lig2_heavy_atoms]) force.addBond([0, 1], [])