def add_reaction_coordinate( system: openmm.System, host_index: List[int], guest_index: List[int], k_z: simtk_unit.Quantity, z_0: simtk_unit.Quantity, force_group: Optional[int] = 11, ): """ Applies the umbrella reaction coordinate to the guest molecule """ # Simple umbrella potential string expression reaction = openmm.CustomCentroidBondForce( 2, "0.5 * k_z * (r_z - z_0)^2;" "r_z = z2 - z1;" ) reaction.setUsesPeriodicBoundaryConditions(False) reaction.setForceGroup(force_group) # Umbrella parameters reaction.addPerBondParameter("k_z", k_z) reaction.addPerBondParameter("z_0", z_0) # Add host and guest indices g1 = reaction.addGroup(host_index) g2 = reaction.addGroup(guest_index) # Add bond reaction.addBond([g1, g2], [k_z, z_0]) # Add force to system system.addForce(reaction)
def add_funnel_potential( system: openmm.System, host_index: List[int], guest_index: List[int], k_xy: Optional[simtk_unit.Quantity] = 10.0 * simtk_unit.kilocalorie_per_mole / simtk_unit.angstrom ** 2, z_cc: Optional[simtk_unit.Quantity] = 11.0 * simtk_unit.angstrom, alpha: Optional[simtk_unit.Quantity] = 35.0 * simtk_unit.degrees, R_cylinder: Optional[simtk_unit.Quantity] = 1.0 * simtk_unit.angstrom, force_group: Optional[int] = 10, ): """ Applies a funnel potential to a guest molecule """ # Funnel potential string expression funnel = openmm.CustomCentroidBondForce( 2, "U_funnel + U_cylinder;" "U_funnel = step(z_cc - abs(r_z))*step(r_xy - R_funnel)*Wall;" "U_cylinder = step(abs(r_z) - z_cc)*step(r_xy - R_cylinder)*Wall;" "Wall = 0.5 * k_xy * r_xy^2;" "R_funnel = (z_cc-abs(r_z))*tan(alpha) + R_cylinder;" "r_xy = sqrt((x2 - x1)^2 + (y2 - y1)^2);" "r_z = z2 - z1;", ) funnel.setUsesPeriodicBoundaryConditions(False) funnel.setForceGroup(force_group) # Funnel parameters funnel.addGlobalParameter("k_xy", k_xy) funnel.addGlobalParameter("z_cc", z_cc) funnel.addGlobalParameter("alpha", alpha) funnel.addGlobalParameter("R_cylinder", R_cylinder) # Add host and guest indices g1 = funnel.addGroup(host_index) g2 = funnel.addGroup(guest_index) # Add bond funnel.addBond([g1, g2], []) # Add force to system system.addForce(funnel)
def add_interactions(self, system, topology): if self.active: rest = self.restraints[0] # convert indices from 1-based to 0-based rest_indices1 = [r - 1 for r in rest.indices1] rest_indices2 = [r - 1 for r in rest.indices2] # create the expression for the energy components = [] if 'x' in rest.dims: components.append('(x1-x2)*(x1-x2)') if 'y' in rest.dims: components.append('(y1-y2)*(y1-y2)') if 'z' in rest.dims: components.append('(z1-z2)*(z1-z2)') dist_expr = 'dist = sqrt({});'.format(' + '.join(components)) energy_expr = '0.5 * com_k * (dist - com_ref_dist)*(dist-com_ref_dist);' expr = '\n'.join([energy_expr, dist_expr]) # create the force force = mm.CustomCentroidBondForce(2, expr) force.addPerBondParameter('com_k') force.addPerBondParameter('com_ref_dist') # create the restraint with parameters if rest.weights1: g1 = force.addGroup(rest_indices1, rest.weights1) else: g1 = force.addGroup(rest_indices1) if rest.weights2: g2 = force.addGroup(rest_indices2, rest.weights2) else: g2 = force.addGroup(rest_indices2) force_const = rest.force_const pos = rest.positioner(0) force.addBond([g1, g2], [force_const, pos]) system.addForce(force) self.force = force return system
def add_interactions(self, system, topology): if self.active: rest = self.restraints[0] # convert indices from 1-based to 0-based indices = [r - 1 for r in rest.indices] # create the expression for the energy components = [] if "x" in rest.dims: components.append("(x1-abscom_x)*(x1-abscom_x)") if "y" in rest.dims: components.append("(y1-abscom_y)*(y1-abscom_y)") if "z" in rest.dims: components.append("(z1-abscom_z)*(z1-abscom_z)") dist_expr = "dist2={};".format(" + ".join(components)) energy_expr = "0.5 * com_k * dist2;" expr = "\n".join([energy_expr, dist_expr]) # create the force force = mm.CustomCentroidBondForce(1, expr) force.addPerBondParameter("com_k") force.addPerBondParameter("abscom_x") force.addPerBondParameter("abscom_y") force.addPerBondParameter("abscom_z") # create the restraint with parameters if rest.weights: g1 = force.addGroup(indices, rest.weights) else: g1 = force.addGroup(indices) force_const = rest.force_const pos_x = rest.position[0] pos_y = rest.position[1] pos_z = rest.position[2] force.addBond([g1], [force_const, pos_x, pos_y, pos_z]) system.addForce(force) self.force = force return system
def add_interactions(self, system, topology): if self.active: rest = self.restraints[0] # convert indices from 1-based to 0-based indices = [r - 1 for r in rest.indices] # create the expression for the energy components = [] if 'x' in rest.dims: components.append('(x1-abscom_x)*(x1-abscom_x)') if 'y' in rest.dims: components.append('(y1-abscom_y)*(y1-abscom_y)') if 'z' in rest.dims: components.append('(z1-abscom_z)*(z1-abscom_z)') dist_expr = 'dist2={};'.format(' + '.join(components)) energy_expr = '0.5 * com_k * dist2;' expr = '\n'.join([energy_expr, dist_expr]) # create the force force = mm.CustomCentroidBondForce(1, expr) force.addPerBondParameter('com_k') force.addPerBondParameter('abscom_x') force.addPerBondParameter('abscom_y') force.addPerBondParameter('abscom_z') # create the restraint with parameters if rest.weights: g1 = force.addGroup(indices, rest.weights) else: g1 = force.addGroup(indices) force_const = rest.force_const pos_x = rest.position[0] pos_y = rest.position[1] pos_z = rest.position[2] force.addBond([g1], [force_const, pos_x, pos_y, pos_z]) system.addForce(force) self.force = force return system
def add_interactions(self, state: interfaces.IState, system: mm.System, topology: app.Topology) -> mm.System: if self.active: rest = self.restraints[0] # create the expression for the energy components = [] if "x" in rest.dims: components.append("(x1-x2)*(x1-x2)") if "y" in rest.dims: components.append("(y1-y2)*(y1-y2)") if "z" in rest.dims: components.append("(z1-z2)*(z1-z2)") dist_expr = "dist = sqrt({});".format(" + ".join(components)) energy_expr = "0.5 * com_k * (dist - com_ref_dist)*(dist-com_ref_dist);" expr = "\n".join([energy_expr, dist_expr]) # create the force force = mm.CustomCentroidBondForce(2, expr) force.addPerBondParameter("com_k") force.addPerBondParameter("com_ref_dist") # create the restraint with parameters if rest.weights1: g1 = force.addGroup(rest.indices1, rest.weights1) else: g1 = force.addGroup(rest.indices1) if rest.weights2: g2 = force.addGroup(rest.indices2, rest.weights2) else: g2 = force.addGroup(rest.indices2) force_const = rest.force_const pos = rest.positioner(0) force.addBond([g1, g2], [force_const, pos]) system.addForce(force) self.force = force return system
def apply_dat_restraint(system, restraint, phase, window_number, flat_bottom=False, force_group=None): """A utility function which takes in pAPRika restraints and applies the restraints to an OpenMM System object. Parameters ---------- system : :class:`openmm.System` The system object to add the positional restraints to. restraint : list List of pAPRika defined restraints phase : str Phase of calculation ("attach", "pull" or "release") window_number : int The corresponding window number of the current phase flat_bottom : bool, optional Specify whether the restraint is a flat bottom potential force_group : int, optional The force group to add the positional restraints to. """ from simtk import openmm, unit assert phase in {"attach", "pull", "release"} if flat_bottom and phase == "attach" and restraint.mask3: flat_bottom_force = openmm.CustomAngleForce( "step(-(theta - theta_0)) * k * (theta - theta_0)^2") # If theta is greater than theta_0, then the argument to step is negative, # which means the force is off. flat_bottom_force.addPerAngleParameter("k") flat_bottom_force.addPerAngleParameter("theta_0") theta_0 = 91.0 * unit.degrees k = (restraint.phase[phase]["force_constants"][window_number] * unit.kilocalories_per_mole / unit.radian**2) flat_bottom_force.addAngle( restraint.index1[0], restraint.index2[0], restraint.index3[0], [k, theta_0], ) system.addForce(flat_bottom_force) if force_group: flat_bottom_force.setForceGroup(force_group) return elif flat_bottom and phase == "attach" and not restraint.mask3: flat_bottom_force = openmm.CustomBondForce( "step((r - r_0)) * k * (r - r_0)^2") # If x is greater than x_0, then the argument to step is positive, which means # the force is on. flat_bottom_force.addPerBondParameter("k") flat_bottom_force.addPerBondParameter("r_0") r_0 = restraint.phase[phase]["targets"][window_number] * unit.angstrom k = (restraint.phase[phase]["force_constants"][window_number] * unit.kilocalories_per_mole / unit.radian**2) flat_bottom_force.addBond( restraint.index1[0], restraint.index2[0], [k, r_0], ) system.addForce(flat_bottom_force) if force_group: flat_bottom_force.setForceGroup(force_group) return elif flat_bottom and phase == "pull": return elif flat_bottom and phase == "release": return if restraint.mask2 and not restraint.mask3: if not restraint.group1 and not restraint.group2: bond_restraint = openmm.CustomBondForce("k * (r - r_0)^2") bond_restraint.addPerBondParameter("k") bond_restraint.addPerBondParameter("r_0") r_0 = restraint.phase[phase]["targets"][ window_number] * unit.angstroms k = (restraint.phase[phase]["force_constants"][window_number] * unit.kilocalories_per_mole / unit.angstrom**2) bond_restraint.addBond(restraint.index1[0], restraint.index2[0], [k, r_0]) system.addForce(bond_restraint) else: bond_restraint = openmm.CustomCentroidBondForce( 2, "k * (distance(g1, g2) - r_0)^2") bond_restraint.addPerBondParameter("k") bond_restraint.addPerBondParameter("r_0") r_0 = restraint.phase[phase]["targets"][ window_number] * unit.angstroms k = (restraint.phase[phase]["force_constants"][window_number] * unit.kilocalories_per_mole / unit.angstrom**2) g1 = bond_restraint.addGroup(restraint.index1) g2 = bond_restraint.addGroup(restraint.index2) bond_restraint.addBond([g1, g2], [k, r_0]) system.addForce(bond_restraint) if force_group: bond_restraint.setForceGroup(force_group) elif restraint.mask3 and not restraint.mask4: if not restraint.group1 and not restraint.group2 and not restraint.group3: angle_restraint = openmm.CustomAngleForce( "k * (theta - theta_0)^2") angle_restraint.addPerAngleParameter("k") angle_restraint.addPerAngleParameter("theta_0") theta_0 = restraint.phase[phase]["targets"][ window_number] * unit.degrees k = (restraint.phase[phase]["force_constants"][window_number] * unit.kilocalories_per_mole / unit.radian**2) angle_restraint.addAngle( restraint.index1[0], restraint.index2[0], restraint.index3[0], [k, theta_0], ) system.addForce(angle_restraint) else: # Probably needs openmm.CustomCentroidAngleForce (?) raise NotImplementedError if force_group: angle_restraint.setForceGroup(force_group) elif restraint.mask4: if (not restraint.group1 and not restraint.group2 and not restraint.group3 and not restraint.group4): dihedral_restraint = openmm.CustomTorsionForce( f"k * min(min(abs(theta - theta_0), abs(theta - theta_0 + 2 * " f"{_PI_})), abs(theta - theta_0 - 2 * {_PI_}))^2") dihedral_restraint.addPerTorsionParameter("k") dihedral_restraint.addPerTorsionParameter("theta_0") theta_0 = restraint.phase[phase]["targets"][ window_number] * unit.degrees k = (restraint.phase[phase]["force_constants"][window_number] * unit.kilocalories_per_mole / unit.radian**2) dihedral_restraint.addTorsion( restraint.index1[0], restraint.index2[0], restraint.index3[0], restraint.index4[0], [k, theta_0], ) system.addForce(dihedral_restraint) else: # Probably needs openmm.CustomCentroidTorsionForce (?) raise NotImplementedError if force_group: dihedral_restraint.setForceGroup(force_group)
# cv.addGroup([int(index) for index in ligand_atoms]) # cv.addGroup([int(index) for index in bottom_atoms], [1.0, 1.0, 1.0]) # cv.addGroup([int(index) for index in top_atoms], [1.0, 1.0, 1.0]) # cv.addBond([0,1,2], []) # system.addForce(cv) md_topology = md.Topology.from_openmm(pdb.topology) topology = pdb.topology bottom_atoms = [1396, 5018, 3076] top_atoms = [6241, 3325, 1626] selection = '(resname CM7) and (mass > 3.0)' print('Determining ligand atoms using "{}"...'.format(selection)) ligand_atoms = md_topology.select(selection) r_parallel = openmm.CustomCentroidBondForce(3, '0') r_parallel.addGroup([int(index) for index in ligand_atoms]) r_parallel.addGroup([int(index) for index in bottom_atoms], [1.0, 1.0, 1.0]) r_parallel.addGroup([int(index) for index in top_atoms], [1.0, 1.0, 1.0]) r_parallel.addBond([0, 1, 2], []) bv = mtd.BiasVariable(r_parallel_bias, -2.0, 3.0, 0.1, False) integrator = openmm.LangevinIntegrator(temperature, collision_rate, timestep) meta = mtd.Metadynamics( openmm_system, [bv], r_parallel, temperature, 10, 5 * unit.kilojoules_per_mole, 1000, saveFrequency=20000,
def runProductionSimulation(equilibrationFiles, workerNumber, outputDir, seed, parameters, reportFileName, checkpoint, ligandName, replica_id, trajsPerReplica, restart=False): """ Functions that runs the production run at NVT conditions. If a boxRadius is defined in the parameters section, a Flat-bottom harmonic restrains will be applied between the protein and the ligand :param equilibrationFiles: Tuple with the paths for the Amber topology file (prmtop) and the pdb for the system :type equilibrationFiles: Tuple :param workerNumber: Number of the subprocess :type workerNumber: int :param outputDir: path to the directory where the output will be written :type outputDir: str :param seed: Seed to use to generate the random numbers :type seed: int :param parameters: Object with the parameters for the simulation :type parameters: :py:class:`/simulationrunner/SimulationParameters` -- SimulationParameters object :param reportFileName: Name for the file where the energy report will be written :type reportFileName: str :param checkpoint: Path to the checkpoint from where the production run will be restarted (Optional) :type checkpoint: str :param ligandName: Code Name for the ligand :type ligandName: str :param replica_id: Id of the replica running :type replica_id: int :param trajsPerReplica: Number of trajectories per replica :type trajsPerReplica: int :param restart: Whether the simulation run has to be restarted or not :type restart: bool """ deviceIndex = workerNumber workerNumber += replica_id * trajsPerReplica + 1 prmtop, pdb = equilibrationFiles prmtop = app.AmberPrmtopFile(prmtop) trajName = os.path.join( outputDir, constants.AmberTemplates.trajectoryTemplate % (workerNumber, parameters.format)) stateReporter = os.path.join(outputDir, "%s_%s" % (reportFileName, workerNumber)) checkpointReporter = os.path.join( outputDir, constants.AmberTemplates.CheckPointReporterTemplate % workerNumber) lastStep = getLastStep(stateReporter) simulation_length = parameters.productionLength - lastStep # if the string is unicode the PDBReaders fails to read the file (this is # probably due to the fact that openmm was built with python2 in my # computer, will need to test thoroughly with python3) pdb = app.PDBFile(str(pdb)) PLATFORM = mm.Platform_getPlatformByName(str(parameters.runningPlatform)) if parameters.runningPlatform == "CUDA": platformProperties = { "Precision": "mixed", "DeviceIndex": getDeviceIndexStr( deviceIndex, parameters.devicesPerTrajectory, devicesPerReplica=parameters.maxDevicesPerReplica), "UseCpuPme": "false" } else: platformProperties = {} system = prmtop.createSystem(nonbondedMethod=app.PME, nonbondedCutoff=parameters.nonBondedCutoff * unit.angstroms, constraints=app.HBonds, removeCMMotion=True) system.addForce( mm.AndersenThermostat(parameters.Temperature * unit.kelvin, 1 / unit.picosecond)) integrator = mm.VerletIntegrator(parameters.timeStep * unit.femtoseconds) system.addForce( mm.MonteCarloBarostat(1 * unit.bar, parameters.Temperature * unit.kelvin)) if parameters.boxRadius: group_ligand = [] group_protein = [] for atom in prmtop.topology.atoms(): if atom.residue.name == ligandName: group_ligand.append(atom.index) elif atom.residue.name not in ("HOH", "Cl-", "Na+"): group_protein.append(atom.index) # Harmonic flat-bottom restrain for the ligand group_ligand = np.array(group_ligand) group_protein = np.array(group_protein) force = mm.CustomCentroidBondForce( 2, 'step(distance(g1,g2)-r) * (k/2) * (distance(g1,g2)-r)^2') force.addGlobalParameter( "k", 5.0 * unit.kilocalories_per_mole / unit.angstroms**2) force.addGlobalParameter("r", parameters.boxRadius * unit.angstroms) force.addGroup(group_protein) force.addGroup(group_ligand) force.addBond( [0, 1], [] ) # the first parameter is the list of indexes of the groups, the second is the list of perbondparameters system.addForce(force) simulation = app.Simulation(prmtop.topology, system, integrator, PLATFORM, platformProperties=platformProperties) simulation.context.setPositions(pdb.positions) if restart: with open(str(checkpoint), 'rb') as check: simulation.context.loadCheckpoint(check.read()) stateData = open(str(stateReporter), "a") else: simulation.context.setVelocitiesToTemperature( parameters.Temperature * unit.kelvin, seed) stateData = open(str(stateReporter), "w") if parameters.format == "xtc": simulation.reporters.append( XTCReporter(str(trajName), parameters.reporterFreq, append=restart)) elif parameters.format == "dcd": simulation.reporters.append( app.DCDReporter(str(trajName), parameters.reporterFreq, append=restart, enforcePeriodicBox=True)) simulation.reporters.append( app.CheckpointReporter(str(checkpointReporter), parameters.reporterFreq)) simulation.reporters.append( CustomStateDataReporter(stateData, parameters.reporterFreq, step=True, potentialEnergy=True, temperature=True, time_sim=True, volume=True, remainingTime=True, speed=True, totalSteps=parameters.productionLength, separator="\t", append=restart, initialStep=lastStep)) if workerNumber == 1: frequency = min(10 * parameters.reporterFreq, parameters.productionLength) simulation.reporters.append( app.StateDataReporter(sys.stdout, frequency, step=True)) simulation.step(simulation_length) stateData.close()
def _create_thermodynamic_states(self, reference_thermodynamic_state, spacing=0.25 * unit.angstroms): """ Create thermodynamic states for sampling along pore axis. Here, the porin is restrained in the (x,z) plane and the pore axis is oriented along the y-axis. Parameters ---------- reference_thermodynamic_state : openmmtools.states.ThermodynamicState Reference ThermodynamicState containing system, temperature, and pressure spacing : simtk.unit.Quantity with units compatible with angstroms Spacing between umbrellas spanning pore axis """ # Determine relevant coordinates # TODO: Allow selections to be specified as class methods axis_center_atom = self.mdtraj_topology.select( 'residue 128 and resname ALA and name CA')[0] axis_center = self._get_reference_coordinates(axis_center_atom) print('axis center (x,z) : {} : {}'.format(axis_center_atom, axis_center)) pore_top_atom = self.mdtraj_topology.select( 'residue 226 and resname SER and name CA')[0] pore_top = self._get_reference_coordinates(pore_top_atom) print('pore top (y-max): {} : {}'.format(pore_top_atom, pore_top)) pore_bottom_atom = self.mdtraj_topology.select( 'residue 88 and resname VAL and name CA')[0] pore_bottom = self._get_reference_coordinates(pore_bottom_atom) print('pore bottom (y-min): {} : {}'.format(pore_bottom_atom, pore_bottom)) # Determine ligand atoms selection = '(residue {}) and (mass > 1.5)'.format(self.ligand_resseq) print('Determining ligand atoms using "{}"...'.format(selection)) ligand_atoms = self.mdtraj_topology.select(selection) if len(ligand_atoms) == 0: raise ValueError('Ligand residue name {} not found'.format( self.ligand_resseq)) print('ligand heavy atoms: {}'.format(ligand_atoms)) # Determine protein atoms bottom_selection = '((residue 342 and resname GLY) or (residue 97 and resname ASP) or (residue 184 and resname SER)) and (name CA)' bottom_protein_atoms = self.mdtraj_topology.select(bottom_selection) top_selection = '((residue 226 and resname SER) or (residue 421 and resname LEU) or (residue 147 and resname GLU)) and (name CA)' top_protein_atoms = self.mdtraj_topology.select(top_selection) # Compute pore bottom (lambda=0) and top (lambda=1) axis_bottom = (axis_center[0], pore_bottom[1], axis_center[2]) axis_top = (axis_center[0], pore_top[1], axis_center[2]) axis_distance = pore_top[1] - pore_bottom[1] print('axis_distance: {}'.format(axis_distance)) # Compute spacing and spring constant 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 # TODO: Come up with a better way to define pore width? scale_factor = 0.25 sigma_xz = scale_factor * unit.sqrt( (axis_center[0] - pore_bottom[0])**2 + (axis_center[2] - pore_bottom[2])** 2) # stddev of force-free fluctuations in xz-plane K_xz = self.kT / (sigma_xz**2) # spring constant print('in-plane sigma_xz = {:.3f} A'.format(sigma_xz / unit.angstroms)) dr = axis_distance * (expansion_factor - 1.0) / 1.0 rmax = axis_distance + dr rmin = -1.0 * axis_distance # Create restraint state that encodes this axis # TODO: Rework this as CustomCVForce with in-plane and along-axis deviations as separate forces so we can store them each iteration print('Creating restraint...') from yank.restraints import RestraintState energy_expression = '(K_parallel/2)*(r_parallel-r0)^2 + (K_orthogonal/2)*r_orthogonal^2;' energy_expression += 'r_parallel = r*cos(theta);' energy_expression += 'r_orthogonal = r*sin(theta);' energy_expression += 'r = distance(g1,g2);' energy_expression += 'theta = angle(g1,g2,g3);' energy_expression += 'r0 = lambda_restraints * (rmax - rmin) + rmin;' force = openmm.CustomCentroidBondForce(3, energy_expression) force.addGlobalParameter('lambda_restraints', 1.0) force.addGlobalParameter('K_parallel', K_y) force.addGlobalParameter('K_orthogonal', K_xz) force.addGlobalParameter('rmax', rmax) force.addGlobalParameter('rmin', rmin) force.addGroup([int(index) for index in ligand_atoms]) force.addGroup([int(index) for index in bottom_protein_atoms]) force.addGroup([int(index) for index in top_protein_atoms]) force.addBond([0, 1, 2], []) self.system.addForce(force) # 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
#============================= external = { "U": extparams['Uext'] * epsilon, "NPeriod": extparams['Nperiod'], "axis": extparams['axis'], "planeLoc": extparams['planeLoc'] } direction = ['x', 'y', 'z'] ax = external["axis"] #atomsInExtField = [elementMap[atomname]] if external["U"] > 0.0 * unit.kilojoules_per_mole: print('\n=== Creating sinusoidal external potential in the {} direction'. format(direction[axis])) energy_function = 'U*sin(2*pi*NPeriod*({axis}1-r0)/L)'.format( axis=direction[ax]) fExt = openmm.CustomCentroidBondForce(1, energy_function) fExt.addGlobalParameter("U", external["U"]) fExt.addGlobalParameter("NPeriod", external["NPeriod"]) fExt.addGlobalParameter("pi", np.pi) fExt.addGlobalParameter("r0", external["planeLoc"]) fExt.addGlobalParameter("L", box_edge[ax]) atomThusFar = 0 for i in range(int(n_p * DOP / mapping)): #looping through CG beads fExt.addGroup( range(atomThusFar, atomThusFar + mapping) ) #assuming the first n_p*DOP atoms are polymer atoms and atom index increases along chain fExt.addBond([i], []) atomThusFar += mapping system.addForce(fExt) #==============================
def main(): print("Reading the PSF file") # Read the PSF file #psf = app.CharmmPsfFile('g1_25mm.psf'); psf = app.CharmmPsfFile('holo_neutr.psf') boxsize = 4.895883 # Boxsize in nm psf.setBox(boxsize * nanometer, boxsize * nanometer, boxsize * nanometer) print("Reading the pdb file") #pdb = app.PDBFile('g1_25mm.pdb'); #pdb = app.PDBFile('md_298k_100ns.pdb') pdb = app.PDBFile('holo_neutr.pdb') # Load the parameter set lib = 'toppar/' #params = app.CharmmParameterSet('toppar/top_all36_cgenff.rtf','toppar/par_all36_cgenff.prm','toppar/cgenff3.0.1/top_all36_cgenff.rtf','toppar/cgenff3.0.1/par_all36_cgenff.prm','g1_new.str','toppar_water_ions.str') #params = app.CharmmParameterSet('toppar/cgenff3.0.1/top_all36_cgenff.rtf','toppar/cgenff3.0.1/par_all36_cgenff.prm','g1_new.str','toppar_water_ions.str') params = app.CharmmParameterSet('toppar/cgenff3.0.1/top_all36_cgenff.rtf', 'toppar/cgenff3.0.1/par_all36_cgenff.prm', 'g1_new.str', 'oah_groups.str', 'toppar_water_ions.str') platform = openmm.Platform.getPlatformByName('CUDA') isShortSim = False #isShortSim = True isPeriodic = True #isPeriodic = False #if not isPeriodic : # isShortSim = True; # Creating the system if (isPeriodic): print("PME is being used") system = psf.createSystem(params, nonbondedMethod=app.PME, nonbondedCutoff=1.2 * nanometer, switchDistance=1.0 * nanometer, ewaldErrorTolerance=0.0001, constraints=app.HBonds) else: print("PME is not being used") system = psf.createSystem( params, # nonbondedMethod=app.PME, nonbondedCutoff=1.2 * nanometer, switchDistance=1.0 * nanometer, ewaldErrorTolerance=0.0001, constraints=app.HBonds) #for force in system.getForces(): # print(force, force.usesPeriodicBoundaryConditions()) # Thermostat @ 298 K #system.addForce(openmm.AndersenThermostat(298*kelvin, 1/picosecond)) # adding the barostat for now #system.addForce(openmm.MonteCarloBarostat(1*bar, 298*kelvin)); # adding positional restriants if isPeriodic: force = openmm.CustomExternalForce( "k*periodicdistance(x, y, z, x0, y0, z0)^2") else: force = openmm.CustomExternalForce("k*((x-x0)^2+(y-y0)^2+(z-z0)^2)") #force = openmm.CustomExternalForce("k*periodicdistance(x, y, z, x0, y0, z0)^2"); force.addGlobalParameter("k", 0.1 * kilocalories_per_mole / angstroms**2) force.addPerParticleParameter("x0") force.addPerParticleParameter("y0") force.addPerParticleParameter("z0") topology = pdb.getTopology() positions = pdb.getPositions() #for atom in topology.atoms(): # print(atom) host = [] guest = [] for res in topology.residues(): if res.name == 'OAH': for atom in res.atoms(): host.append(atom.index) force.addParticle(atom.index, positions[atom.index]) #force.addParticle(atom.index, (0, 0, 0)*nanometers) if res.name == 'GOA': for atom in res.atoms(): guest.append(atom.index) system.addForce(force) print("Does customExternalForce use periodic boundary condition : ", force.usesPeriodicBoundaryConditions()) # adding restraint between the host and guest # this will be inside the umbrella sampling loop host_guest_centroid_force = openmm.CustomCentroidBondForce( 2, "0.5*k*(distance(g1,g2)-d0)^2") host_guest_centroid_force.addGlobalParameter( "k", 10.0 * kilocalories_per_mole / angstroms**2) #d0_global_parameter_index = force.addGlobalParameter("d0", 2.0*angstroms); #d0_perBond_parameter_index = force.addPerBondParameter("d0", 2.0*angstroms); d0_perBond_parameter_index = host_guest_centroid_force.addPerBondParameter( "d0") group1 = host_guest_centroid_force.addGroup(host) group2 = host_guest_centroid_force.addGroup(guest) host_guest_bond = host_guest_centroid_force.addBond([group1, group2]) host_guest_centroid_force.setBondParameters(host_guest_bond, [group1, group2], [20 * angstroms]) system.addForce(host_guest_centroid_force) #sys.exit(0) """ # Restrain along z axis # adding positional restriants if isPeriodic : z_force = openmm.CustomExternalForce("k*periodicdistance(x, x0)^2"); else : z_force = openmm.CustomExternalForce("k*((x-x0)^2+(y-y0)^2)"); #force = openmm.CustomExternalForce("k*periodicdistance(x, y, z, x0, y0, z0)^2"); z_force.addGlobalParameter("k", 0.1*kilocalories_per_mole/angstroms**2); z_force.addPerParticleParameter("x0"); z_force.addPerParticleParameter("y0"); for res in topology.residues(): if res.name == 'GOA' : for atom in res.atoms(): pos = list(positions[atom.index]) print(pos[2]) z_force.addParticle(atom.index, [pos[0], pos[1]]) system.addForce(z_force) """ # langevin integrator integrator = openmm.LangevinIntegrator( 298 * kelvin, # Temperature 1.0 / picoseconds, # Friction coefficient 0.001 * picoseconds # Time step ) #integrator = openmm.VerletIntegrator(0.001*picoseconds); simulation = app.Simulation(psf.topology, system, integrator, platform) simulation.context.setPositions(pdb.getPositions()) #currentState = simulation.context.getState(getPositions=True) #pdbr = app.PDBReporter("pdbreport.pdb",1); #pdbr.report(simulation, currentState) print("Minimizing...") simulation.minimizeEnergy(maxIterations=2000) print("Equilibrating...") simulation.context.setVelocitiesToTemperature(298 * kelvin) #currentState = simulation.context.getState(getPositions=True) #pdbr = app.PDBReporter("pdbreport_after_min.pdb",1); #pdbr.report(simulation, currentState) #pdbr = app.PDBReporter("pdbreport_after_step1.pdb",1); nsavcrd = 10000 # save coordinates every 10 ps nprint = 2000 # report every 2 ps if isShortSim: nstep = 20000 # run the simulation for 0.2ns else: nstep = 2000000 # run the simulation for 2ns firstDcdStep = nsavcrd # Reporters dcdReportInterval = 10000 # save coordinates every 10 ps dcd = app.DCDReporter('umb_3.dcd', dcdReportInterval) dcd._dcd = app.DCDFile(dcd._out, simulation.topology, simulation.integrator.getStepSize(), firstDcdStep, nsavcrd) stateReporter = app.StateDataReporter('umb_3.out', nprint, step=True, kineticEnergy=True, potentialEnergy=True, totalEnergy=True, temperature=True, volume=True, speed=True) simulation.reporters.append(dcd) simulation.reporters.append(stateReporter) #simulation.reporters.append(pdbr); #simulation.reporters.append(app.StateDataReporter('umb.out', nprint, step=True, kineticEnergy=True, potentialEnergy=True, totalEnergy=True, temperature=True, volume=True, speed=True)) # Run the simulation print("Simulation begins ...") #simulation.step(1) #sys.exit(0) for i in range(15): print("Simulation for umbrella : ", i) host_guest_centroid_force.setBondParameters(host_guest_bond, [group1, group2], [(5 + 3 * i) * angstroms]) #force.setGlobalParameterDefaultValue(d0_global_parameter_index, (2+i)*angstroms) host_guest_centroid_force.updateParametersInContext(simulation.context) print("host guest bond parameters", host_guest_centroid_force.getBondParameters(host_guest_bond)) #serialized = openmm.XmlSerializer.serialize(simulation.system) #of = open("serial_sim_"+str(i)+".xml",'w'); #of.write(serialized); #of.close(); #simulation.saveState("state_"+str(i)+".xml"); simulation.step(nstep) simulation.reporters.pop() simulation.reporters.pop() dcd._out.close()
def setup_openmm_restraints(system, restraint, phase, window): """ Add particle restraints with OpenMM. """ # http://docs.openmm.org/7.1.0/api-c++/generated/OpenMM.CustomExternalForce.html # It's possible we might need to use `periodicdistance`. if (restraint.mask1 is not None and restraint.mask2 is not None and restraint.mask3 is None and restraint.mask4 is None): if restraint.group1 is False and restraint.group2 is False: bond_restraint = mm.CustomBondForce("k * (r - r_0)^2") bond_restraint.addPerBondParameter("k") bond_restraint.addPerBondParameter("r_0") r_0 = restraint.phase[phase]["targets"][window] * unit.angstrom k = (restraint.phase[phase]["force_constants"][window] * unit.kilocalorie_per_mole / unit.angstrom**2) bond_restraint.addBond(restraint.index1[0], restraint.index2[0], [k, r_0]) bond_restraint.setForceGroup(1) system.addForce(bond_restraint) log.debug( "Added bond restraint between {} and {} with target value = " "{} and force constant = {}".format(restraint.mask1, restraint.mask2, r_0, k)) elif restraint.group1 is True or restraint.group2 is True: # http://docs.openmm.org/7.0.0/api-python/generated/simtk.openmm.openmm.CustomManyParticleForce.html # http://getyank.org/development/_modules/yank/restraints.html bond_restraint = mm.CustomCentroidBondForce( 2, "k * (distance(g1, g2) - r_0)^2") bond_restraint.addPerBondParameter("k") bond_restraint.addPerBondParameter("r_0") r_0 = restraint.phase[phase]["targets"][window] * unit.angstrom k = (restraint.phase[phase]["force_constants"][window] * unit.kilocalorie_per_mole / unit.angstrom**2) g1 = bond_restraint.addGroup(restraint.index1) g2 = bond_restraint.addGroup(restraint.index2) bond_restraint.addBond([g1, g2], [k, r_0]) bond_restraint.setForceGroup(1) system.addForce(bond_restraint) log.debug( "Added bond restraint between {} and {} with target value = " "{} and force constant = {}".format(restraint.mask1, restraint.mask2, r_0, k)) else: log.error("Unable to add bond restraint...") log.debug("restraint.index1 = {}".format(restraint.index1)) log.debug("restraint.index2 = {}".format(restraint.index2)) raise Exception("Unable to add bond restraint...") if (restraint.mask1 is not None and restraint.mask2 is not None and restraint.mask3 is not None and restraint.mask4 is None): if (restraint.group1 is not False and restraint.group2 is not False and restraint.group3 is not False): log.error("Unable to add a group angle restraint...") log.debug("restraint.index1 = {}".format(restraint.index1)) log.debug("restraint.index2 = {}".format(restraint.index2)) log.debug("restraint.index3 = {}".format(restraint.index3)) raise Exception("Unable to add a group angle restraint...") angle_restraint = mm.CustomAngleForce("k * (theta - theta_0)^2") angle_restraint.addPerAngleParameter("k") angle_restraint.addPerAngleParameter("theta_0") log.debug("Setting an angle restraint in degrees using a " "force constant in kcal per mol rad**2...") theta_0 = restraint.phase[phase]["targets"][window] * unit.degrees k = (restraint.phase[phase]["force_constants"][window] * unit.kilocalorie_per_mole / unit.radian**2) angle_restraint.addAngle(restraint.index1[0], restraint.index2[0], restraint.index3[0], [k, theta_0]) system.addForce(angle_restraint) if (restraint.mask1 is not None and restraint.mask2 is not None and restraint.mask3 is not None and restraint.mask4 is not None): if (restraint.group1 is not False and restraint.group2 is not False and restraint.group3 is not False and restraint.group4 is not False): log.error("Unable to add a group dihedral restraint...") log.debug("restraint.index1 = {}".format(restraint.index1)) log.debug("restraint.index2 = {}".format(restraint.index2)) log.debug("restraint.index3 = {}".format(restraint.index3)) log.debug("restraint.index4 = {}".format(restraint.index4)) raise Exception("Unable to add a group dihedral restraint...") dihedral_restraint = mm.CustomTorsionForce("k * (theta - theta_0)^2") dihedral_restraint.addPerTorsionParameter("k") dihedral_restraint.addPerTorsionParameter("theta_0") log.debug("Setting a torsion restraint in degrees using a " "force constant in kcal per mol rad**2...") theta_0 = restraint.phase[phase]["targets"][window] * unit.degrees k = (restraint.phase[phase]["force_constants"][window] * unit.kilocalorie_per_mole / unit.radian**2) dihedral_restraint.addTorsion( restraint.index1[0], restraint.index2[0], restraint.index3[0], restraint.index4[0], [k, theta_0], ) system.addForce(dihedral_restraint) return system
def main(args): #Get the structure and topology files from the command line #ParmEd accepts a wide range of file types (Amber, GROMACS, CHARMM, OpenMM... but not LAMMPS) try: topFile = args[0] strucFile = args[1] except IndexError: print("Specify topology and structure files from the command line.") sys.exit(2) print("Using topology file: %s" % topFile) print("Using structure file: %s" % strucFile) print("\nSetting up system...") #Load in the files for initial simulations top = pmd.load_file(topFile) struc = pmd.load_file(strucFile) #Transfer unit cell information to topology object top.box = struc.box[:] #Set up some global features to use in all simulations temperature = 298.15 * u.kelvin #Define the platform (i.e. hardware and drivers) to use for running the simulation #This can be CUDA, OpenCL, CPU, or Reference #CUDA is for NVIDIA GPUs #OpenCL is for CPUs or GPUs, but must be used for old CPUs (not SSE4.1 compatible) #CPU only allows single precision (CUDA and OpenCL allow single, mixed, or double) #Reference is a clear, stable reference for other code development and is very slow, using double precision by default platform = mm.Platform.getPlatformByName('CUDA') prop = { #'Threads': '2', #number of threads for CPU - all definitions must be strings (I think) 'Precision': 'mixed', #for CUDA or OpenCL, select the precision (single, mixed, or double) 'DeviceIndex': '0', #selects which GPUs to use - set this to zero if using CUDA_VISIBLE_DEVICES 'DeterministicForces': 'True' #Makes sure forces with CUDA and PME are deterministic } #Create the OpenMM system that can be used as a reference systemRef = top.createSystem( nonbondedMethod=app. PME, #Uses PME for long-range electrostatics, simple cut-off for LJ nonbondedCutoff=12.0 * u.angstroms, #Defines cut-off for non-bonded interactions rigidWater=True, #Use rigid water molecules constraints=app.HBonds, #Constrains all bonds involving hydrogens flexibleConstraints= False, #Whether to include energies for constrained DOFs removeCMMotion= False, #Whether or not to remove COM motion (don't want to if part of system frozen) ) #Set up the integrator to use as a reference integratorRef = mm.LangevinIntegrator( temperature, #Temperature for Langevin 1.0 / u.picoseconds, #Friction coefficient 2.0 * u.femtoseconds, #Integration timestep ) integratorRef.setConstraintTolerance(1.0E-08) #To freeze atoms, set mass to zero (does not apply to virtual sites, termed "extra particles" in OpenMM) #Here assume (correctly, I think) that the topology indices for atoms correspond to those in the system for i, atom in enumerate(top.atoms): if atom.type in ('SU'): #, 'CU', 'CUO'): systemRef.setParticleMass(i, 0 * u.dalton) #Track non-bonded force, mainly to turn off dispersion correction NBForce = None for frc in systemRef.getForces(): if (isinstance(frc, mm.NonbondedForce)): NBForce = frc #Turn off dispersion correction since have interface NBForce.setUseDispersionCorrection(False) #Get solute atoms and solute heavy atoms separately soluteIndices = [] heavyIndices = [] for res in top.residues: if res.name not in ['OTM', 'CTM', 'STM', 'NTM', 'SOL']: for atom in res.atoms: soluteIndices.append(atom.idx) if 'H' not in atom.name[0]: heavyIndices.append(atom.idx) #JUST for boric acid, add a custom bonded force #Couldn't find a nice, compatible force field, but did find A forcefield, so using it #But has no angle terms on O-B-O and instead a weird bond repulsion term #This term also prevents out of plane bending #Simple in our case because boric acid is symmetric, so only need one parameter #Parameters come from Otkidach and Pletnev, 2001 #Here, Ad = (A^2) / (d^6) since Ai and Aj and di and dj are all the same #In the original paper, B-OH bond had A = 1.72 and d = 0.354 #Note that d is dimensionless and A should have units of (Angstrom^3)*(kcal/mol)^(1/2) #These units are inferred just to make things work out with kcal/mol and the given distance dependence bondRepulsionFunction = 'Ad*(1.0/r)^6' BondRepulsionForce = mm.CustomBondForce(bondRepulsionFunction) BondRepulsionForce.addPerBondParameter( 'Ad') #Units are technically kJ/mol * nm^6 baOxInds = [] for aind in soluteIndices: if top.atoms[aind].type == 'oh': baOxInds.append(aind) for i in range(len(baOxInds)): for j in range(i + 1, len(baOxInds)): BondRepulsionForce.addBond(baOxInds[i], baOxInds[j], [0.006289686]) systemRef.addForce(BondRepulsionForce) #Also get surface SU atoms surfIndices = [] for atom in top.atoms: if atom.type == 'SU': surfIndices.append(atom.idx) startPos = np.array(struc.positions.value_in_unit(u.nanometer)) print("\nSolute indices: %s" % str(soluteIndices)) print("Solute heavy atom indices: %s" % str(heavyIndices)) print("Surface SU atom indices: %s" % str(surfIndices)) #Solute should already be placed far from the surface #If this is not done right, or it is too close to half the periodic box distance, will have issues #Either way, set this as the starting reference z distance initRefZ = np.average(startPos[heavyIndices, 2]) - np.average( startPos[surfIndices, 2]) print(initRefZ) #Will now add a custom bonded force between solute heavy atoms and surface SU atoms #Should be in units of kJ/mol*nm^2, but should check this #For expanded ensemble, fine to assume solute is less than half the box distance from the surface #But for umbrella sampling, want to apply pulling towards surface regardless of whether pull from above or below #Also allows us to get further from the surface with our umbrellas without worrying about PBCs restraintExpression = '0.5*k*(abs(z2 - z1) - refZ)^2' restraintForce = mm.CustomCentroidBondForce(2, restraintExpression) restraintForce.addPerBondParameter('k') restraintForce.addGlobalParameter( 'refZ', initRefZ) #Make global so can modify during simulation restraintForce.addGroup(surfIndices, np.ones( len(surfIndices))) #Don't weight with masses restraintForce.addGroup(heavyIndices, np.ones(len(heavyIndices))) restraintForce.addBond([0, 1], [1000.0]) restraintForce.setUsesPeriodicBoundaryConditions( True) #Only when doing umbrella sampling systemRef.addForce(restraintForce) forceLabelsRef = getForceLabels(systemRef) decompEnergy(systemRef, struc.positions, labels=forceLabelsRef, verbose=False) #Do NVT simulation stateFileNVT1, stateNVT1 = doSimNVT(top, systemRef, integratorRef, platform, prop, temperature, pos=struc.positions) #Do pulling simulation - really I'm just slowly changing the equilibrium bond distance between the surface and the solute stateFilePull, statePull = doSimPull(top, systemRef, integratorRef, platform, prop, temperature, state=stateFileNVT1) decompEnergy(systemRef, statePull, labels=forceLabelsRef, verbose=False) #Load in pulling restraint data and pulling trajectory to identify starting structures for each umbrella pullData = np.loadtxt('pull_restraint.txt') trajtop = copy.deepcopy(top) trajtop.rb_torsions = pmd.TrackedList([]) trajtop = pt.load_parmed(trajtop, traj=False) pulltraj = pt.iterload('pull.nc', trajtop) frameTimes = np.array([frame.time for frame in pulltraj]) #Define some umbrella distances based on a fixed spacing between initial and final pulling coordinate #Actually for final pulling coordinate, close to the surface, using average of actual coordinate and reference zSpace = 0.1 #nm zUmbs = np.arange(0.5 * (pullData[-1, 1] + pullData[-1, 2]), pullData[0, 2], zSpace) print("\nUsing following umbrellas:") print(zUmbs) #Then loop over umbrellas and run simulation for each for i, zRefDist in enumerate(zUmbs): os.mkdir("umbrella%i" % i) os.chdir("umbrella%i" % i) #Find where in the pulling trajectory the solute came closest to this umbrella pullDatInd = np.argmin(abs(pullData[:, 2] - zRefDist)) frameInd = np.argmin(abs(frameTimes - pullData[pullDatInd, 0])) #Get starting coordinates #Making sure to assign the units that will be returned by pytraj thisCoords = np.array(pulltraj[frameInd].xyz) * u.angstrom #Set reference value for harmonic force restraintForce.setGlobalParameterDefaultValue(0, zRefDist) print("\nUmbrella %i:" % i) print("\tReference distance: %f" % zRefDist) print("\tFrame chosen from trajectory: %i (%f ps)" % (frameInd, frameTimes[frameInd])) #And run simulations, first equilibrating in NVT, then NPT, then production in NPT stateFileNVT, stateNVT = doSimNVT(top, systemRef, integratorRef, platform, prop, temperature, pos=thisCoords) stateFileNPT, stateNPT = doSimNPT(top, systemRef, integratorRef, platform, prop, temperature, state=stateFileNVT) stateFileProd, stateProd = doSimUmbrella(top, systemRef, integratorRef, platform, prop, temperature, state=stateFileNPT) os.chdir("../")
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 apply_openmm_restraints(system, restraint, window, flat_bottom=False, ForceGroup=None): if window[0] == "a": phase = "attach" elif window[0] == "p": phase = "pull" elif window[0] == "r": phase = "release" window_number = int(window[1:]) if flat_bottom and phase == "attach" and restraint.mask3: flat_bottom_force = openmm.CustomAngleForce( 'step(-(theta - theta_0)) * k * (theta - theta_0)^2') # If theta is greater than theta_0, then the argument to step is negative, which means the force is off. flat_bottom_force.addPerAngleParameter("k") flat_bottom_force.addPerAngleParameter("theta_0") theta_0 = 91.0 * unit.degrees k = (restraint.phase[phase]["force_constants"][window_number] * unit.kilocalories_per_mole / unit.radian**2) flat_bottom_force.addAngle( restraint.index1[0], restraint.index2[0], restraint.index3[0], [k, theta_0], ) system.addForce(flat_bottom_force) if ForceGroup: flat_bottom_force.setForceGroup(ForceGroup) return system elif flat_bottom and phase == "attach" and not restraint.mask3: flat_bottom_force = openmm.CustomBondForce( 'step((r - r_0)) * k * (r - r_0)^2') # If x is greater than x_0, then the argument to step is positive, which means the force is on. flat_bottom_force.addPerBondParameter("k") flat_bottom_force.addPerBondParameter("r_0") r_0 = restraint.phase[phase]["targets"][window_number] * unit.angstrom k = (restraint.phase[phase]["force_constants"][window_number] * unit.kilocalories_per_mole / unit.radian**2) flat_bottom_force.addBond( restraint.index1[0], restraint.index2[0], [k, r_0], ) system.addForce(flat_bottom_force) if ForceGroup: flat_bottom_force.setForceGroup(ForceGroup) return system elif flat_bottom and phase == "pull": return system elif flat_bottom and phase == "release": return system if restraint.mask2 and not restraint.mask3: if not restraint.group1 and not restraint.group2: bond_restraint = openmm.CustomBondForce("k * (r - r_0)^2") bond_restraint.addPerBondParameter("k") bond_restraint.addPerBondParameter("r_0") r_0 = restraint.phase[phase]["targets"][ window_number] * unit.angstroms k = (restraint.phase[phase]["force_constants"][window_number] * unit.kilocalories_per_mole / unit.angstrom**2) bond_restraint.addBond(restraint.index1[0], restraint.index2[0], [k, r_0]) system.addForce(bond_restraint) else: bond_restraint = openmm.CustomCentroidBondForce( 2, "k * (distance(g1, g2) - r_0)^2") bond_restraint.addPerBondParameter("k") bond_restraint.addPerBondParameter("r_0") r_0 = restraint.phase[phase]["targets"][ window_number] * unit.angstroms k = (restraint.phase[phase]["force_constants"][window_number] * unit.kilocalories_per_mole / unit.angstrom**2) g1 = bond_restraint.addGroup(restraint.index1) g2 = bond_restraint.addGroup(restraint.index2) bond_restraint.addBond([g1, g2], [k, r_0]) system.addForce(bond_restraint) if ForceGroup: bond_restraint.setForceGroup(ForceGroup) elif restraint.mask3 and not restraint.mask4: if not restraint.group1 and not restraint.group2 and not restraint.group3: angle_restraint = openmm.CustomAngleForce( "k * (theta - theta_0)^2") angle_restraint.addPerAngleParameter("k") angle_restraint.addPerAngleParameter("theta_0") theta_0 = restraint.phase[phase]["targets"][ window_number] * unit.degrees k = (restraint.phase[phase]["force_constants"][window_number] * unit.kilocalories_per_mole / unit.radian**2) angle_restraint.addAngle( restraint.index1[0], restraint.index2[0], restraint.index3[0], [k, theta_0], ) system.addForce(angle_restraint) else: # Probably needs openmm.CustomCentroidAngleForce (?) raise NotImplementedError if ForceGroup: angle_restraint.setForceGroup(ForceGroup) elif restraint.mask4: if (not restraint.group1 and not restraint.group2 and not restraint.group3 and not restraint.group4): dihedral_restraint = openmm.CustomTorsionForce( f"k * min(min(abs(theta - theta_0), abs(theta - theta_0 + 2 * {_PI_})), abs(theta - theta_0 - 2 * {_PI_}))^2" ) dihedral_restraint.addPerTorsionParameter("k") dihedral_restraint.addPerTorsionParameter("theta_0") theta_0 = restraint.phase[phase]["targets"][ window_number] * unit.degrees k = (restraint.phase[phase]["force_constants"][window_number] * unit.kilocalories_per_mole / unit.radian**2) dihedral_restraint.addTorsion( restraint.index1[0], restraint.index2[0], restraint.index3[0], restraint.index4[0], [k, theta_0], ) system.addForce(dihedral_restraint) else: # Probably needs openmm.CustomCentroidTorsionForce (?) raise NotImplementedError if ForceGroup: dihedral_restraint.setForceGroup(ForceGroup) return system
def main(args): #Get the structure and topology files from the command line #ParmEd accepts a wide range of file types (Amber, GROMACS, CHARMM, OpenMM... but not LAMMPS) try: topFile = args[0] strucFile = args[1] except IndexError: print("Specify topology and structure files from the command line.") sys.exit(2) print("Using topology file: %s" % topFile) print("Using structure file: %s" % strucFile) print("\nSetting up system...") #Load in the files for initial simulations top = pmd.load_file(topFile) struc = pmd.load_file(strucFile) #Transfer unit cell information to topology object top.box = struc.box[:] #Set up some global features to use in all simulations temperature = 298.15 * u.kelvin #Define the platform (i.e. hardware and drivers) to use for running the simulation #This can be CUDA, OpenCL, CPU, or Reference #CUDA is for NVIDIA GPUs #OpenCL is for CPUs or GPUs, but must be used for old CPUs (not SSE4.1 compatible) #CPU only allows single precision (CUDA and OpenCL allow single, mixed, or double) #Reference is a clear, stable reference for other code development and is very slow, using double precision by default platform = mm.Platform.getPlatformByName('CUDA') prop = { #'Threads': '2', #number of threads for CPU - all definitions must be strings (I think) 'Precision': 'mixed', #for CUDA or OpenCL, select the precision (single, mixed, or double) 'DeviceIndex': '0', #selects which GPUs to use - set this to zero if using CUDA_VISIBLE_DEVICES 'DeterministicForces': 'True' #Makes sure forces with CUDA and PME are deterministic } #Create the OpenMM system that can be used as a reference systemRef = top.createSystem( nonbondedMethod=app. PME, #Uses PME for long-range electrostatics, simple cut-off for LJ nonbondedCutoff=12.0 * u.angstroms, #Defines cut-off for non-bonded interactions rigidWater=True, #Use rigid water molecules constraints=app.HBonds, #Constrains all bonds involving hydrogens flexibleConstraints= False, #Whether to include energies for constrained DOFs removeCMMotion= False, #Whether or not to remove COM motion (don't want to if part of system frozen) ) #Set up the integrator to use as a reference integratorRef = mm.LangevinIntegrator( temperature, #Temperature for Langevin 1.0 / u.picoseconds, #Friction coefficient 2.0 * u.femtoseconds, #Integration timestep ) integratorRef.setConstraintTolerance(1.0E-08) #To freeze atoms, set mass to zero (does not apply to virtual sites, termed "extra particles" in OpenMM) #Here assume (correctly, I think) that the topology indices for atoms correspond to those in the system for i, atom in enumerate(top.atoms): if atom.type in ('SU'): #, 'CU', 'CUO'): systemRef.setParticleMass(i, 0 * u.dalton) #Get solute atoms and solute heavy atoms separately #Do this for as many solutes as we have so get list for each soluteIndices = [] heavyIndices = [] for res in top.residues: if res.name not in ['OTM', 'CTM', 'STM', 'NTM', 'SOL']: thissolinds = [] thisheavyinds = [] for atom in res.atoms: thissolinds.append(atom.idx) if 'H' not in atom.name[0]: thisheavyinds.append(atom.idx) soluteIndices.append(thissolinds) heavyIndices.append(thisheavyinds) #For convenience, also create flattened version of soluteIndices allSoluteIndices = [] for inds in soluteIndices: allSoluteIndices += inds #Also get surface SU atoms and surface CU atoms at top and bottom of surface surfIndices = [] for atom in top.atoms: if atom.type == 'SU': surfIndices.append(atom.idx) print("\nSolute indices: %s" % str(soluteIndices)) print("(all together - %s)" % str(allSoluteIndices)) print("Solute heavy atom indices: %s" % str(heavyIndices)) print("Surface SU atom indices: %s" % str(surfIndices)) #Will now add a custom bonded force between heavy atoms of each solute and surface SU atoms #Should be in units of kJ/mol*nm^2, but should check this #Also, note that here we are using a flat-bottom restraint to keep close to surface #AND to keep from penetrating into surface when it's in the decoupled state refZlo = 1.4 * u.nanometer #in nm, the distance between the SU atoms and the solute centroid refZhi = 1.7 * u.nanometer restraintExpression = '0.5*k*step(refZlo - (z2 - z1))*(((z2 - z1) - refZlo)^2)' restraintExpression += '+ 0.5*k*step((z2 - z1) - refZhi)*(((z2 - z1) - refZhi)^2)' restraintForce = mm.CustomCentroidBondForce(2, restraintExpression) restraintForce.addPerBondParameter('k') restraintForce.addPerBondParameter('refZlo') restraintForce.addPerBondParameter('refZhi') restraintForce.addGroup(surfIndices, np.ones( len(surfIndices))) #Don't weight with masses #To assign flat-bottom restraint correctly, need to know if each solute is above or below interface #Will need surface z-positions for this suZpos = np.average(struc.coordinates[surfIndices, 2]) for i in range(len(heavyIndices)): restraintForce.addGroup(heavyIndices[i], np.ones(len(heavyIndices[i]))) solZpos = np.average(struc.coordinates[heavyIndices[i], 2]) if (solZpos - suZpos) > 0: restraintForce.addBond([0, i + 1], [10000.0, refZlo, refZhi]) else: #A little confusing, but have to negate and switch for when z2-z1 is always going to be negative restraintForce.addBond([0, i + 1], [10000.0, -refZhi, -refZlo]) systemRef.addForce(restraintForce) #Setting up the alchemical system so we can repeat the calculation with a decoupled particle #We need to add a custom non-bonded force for the solute being alchemically changed #Will be helpful to have handle on non-bonded force handling LJ and coulombic interactions NBForce = None for frc in systemRef.getForces(): if (isinstance(frc, mm.NonbondedForce)): NBForce = frc #Turn off dispersion correction since have interface NBForce.setUseDispersionCorrection(False) forceLabelsRef = getForceLabels(systemRef) decompEnergy(systemRef, struc.positions, labels=forceLabelsRef) #Separate out alchemical and regular particles using set objects alchemicalParticles = set(allSoluteIndices) chemicalParticles = set(range( systemRef.getNumParticles())) - alchemicalParticles #Define the soft-core function for turning on/off LJ interactions #In energy expressions for CustomNonbondedForce, r is a special variable and refers to the distance between particles #All other variables must be defined somewhere in the function. #The exception are variables like sigma1 and sigma2. #It is understood that a parameter will be added called 'sigma' and that the '1' and '2' are to specify the combining rule. softCoreFunction = '4.0*lambdaLJ*epsilon*x*(x-1.0); x = (1.0/reff_sterics);' softCoreFunction += 'reff_sterics = (0.5*(1.0-lambdaLJ) + ((r/sigma)^6));' softCoreFunction += 'sigma=0.5*(sigma1+sigma2); epsilon = sqrt(epsilon1*epsilon2)' #Define the system force for this function and its parameters SoftCoreForce = mm.CustomNonbondedForce(softCoreFunction) SoftCoreForce.addGlobalParameter( 'lambdaLJ', 1.0 ) #Throughout, should follow convention that lambdaLJ=1.0 is fully-interacting state SoftCoreForce.addPerParticleParameter('sigma') SoftCoreForce.addPerParticleParameter('epsilon') #Will turn off electrostatics completely in the original non-bonded force #In the end-state, only want electrostatics inside the alchemical molecule #To do this, just turn ON a custom force as we turn OFF electrostatics in the original force ONE_4PI_EPS0 = 138.935456 #in kJ/mol nm/e^2 soluteCoulFunction = '(1.0-(lambdaQ^2))*ONE_4PI_EPS0*charge/r;' soluteCoulFunction += 'ONE_4PI_EPS0 = %.16e;' % (ONE_4PI_EPS0) soluteCoulFunction += 'charge = charge1*charge2' SoluteCoulForce = mm.CustomNonbondedForce(soluteCoulFunction) #Note this lambdaQ will be different than for soft core (it's also named differently, which is CRITICAL) #This lambdaQ corresponds to the lambda that scales the charges to zero #To turn on this custom force at the same rate, need to multiply by (1.0-lambdaQ**2), which we do SoluteCoulForce.addGlobalParameter('lambdaQ', 1.0) SoluteCoulForce.addPerParticleParameter('charge') #Also create custom force for intramolecular alchemical LJ interactions #Could include with electrostatics, but nice to break up #We could also do this with a separate NonbondedForce object, but it would be a little more work, actually soluteLJFunction = '4.0*epsilon*x*(x-1.0); x = (sigma/r)^6;' soluteLJFunction += 'sigma=0.5*(sigma1+sigma2); epsilon=sqrt(epsilon1*epsilon2)' SoluteLJForce = mm.CustomNonbondedForce(soluteLJFunction) SoluteLJForce.addPerParticleParameter('sigma') SoluteLJForce.addPerParticleParameter('epsilon') #Loop over all particles and add to custom forces #As we go, will also collect full charges on the solute particles #AND we will set up the solute-solute interaction forces alchemicalCharges = [[0]] * len(allSoluteIndices) for ind in range(systemRef.getNumParticles()): #Get current parameters in non-bonded force [charge, sigma, epsilon] = NBForce.getParticleParameters(ind) #Make sure that sigma is not set to zero! Fine for some ways of writing LJ energy, but NOT OK for soft-core! if sigma / u.nanometer == 0.0: newsigma = 0.3 * u.nanometer #This 0.3 is what's used by GROMACS as a default value for sc-sigma else: newsigma = sigma #Add the particle to the soft-core force (do for ALL particles) SoftCoreForce.addParticle([newsigma, epsilon]) #Also add the particle to the solute only forces SoluteCoulForce.addParticle([charge]) SoluteLJForce.addParticle([sigma, epsilon]) #If the particle is in the alchemical molecule, need to set it's LJ interactions to zero in original force if ind in allSoluteIndices: NBForce.setParticleParameters(ind, charge, sigma, epsilon * 0.0) #And keep track of full charge so we can scale it right by lambda alchemicalCharges[allSoluteIndices.index(ind)] = charge #Now we need to handle exceptions carefully for ind in range(NBForce.getNumExceptions()): [p1, p2, excCharge, excSig, excEps] = NBForce.getExceptionParameters(ind) #For consistency, must add exclusions where we have exceptions for custom forces SoftCoreForce.addExclusion(p1, p2) SoluteCoulForce.addExclusion(p1, p2) SoluteLJForce.addExclusion(p1, p2) #Only compute interactions between the alchemical and other particles for the soft-core force SoftCoreForce.addInteractionGroup(alchemicalParticles, chemicalParticles) #And only compute alchemical/alchemical interactions for other custom forces SoluteCoulForce.addInteractionGroup(alchemicalParticles, alchemicalParticles) SoluteLJForce.addInteractionGroup(alchemicalParticles, alchemicalParticles) #Set other soft-core parameters as needed SoftCoreForce.setCutoffDistance(12.0 * u.angstroms) SoftCoreForce.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic) SoftCoreForce.setUseLongRangeCorrection(False) systemRef.addForce(SoftCoreForce) #Set other parameters as needed - note that for the solute force would like to set no cutoff #However, OpenMM won't allow a bunch of potentials with cutoffs then one without... #So as long as the solute is smaller than the cut-off, won't have any problems! SoluteCoulForce.setCutoffDistance(12.0 * u.angstroms) SoluteCoulForce.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic) SoluteCoulForce.setUseLongRangeCorrection(False) systemRef.addForce(SoluteCoulForce) SoluteLJForce.setCutoffDistance(12.0 * u.angstroms) SoluteLJForce.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic) SoluteLJForce.setUseLongRangeCorrection(False) systemRef.addForce(SoluteLJForce) #First do simulation with fully coupled state SoftCoreForce.setGlobalParameterDefaultValue(0, 1.0) SoluteCoulForce.setGlobalParameterDefaultValue(0, 1.0) for k, ind in enumerate(allSoluteIndices): [charge, sig, eps] = NBForce.getParticleParameters(ind) NBForce.setParticleParameters(ind, alchemicalCharges[k] * 1.0, sig, eps) forceLabelsRef = getForceLabels(systemRef) decompEnergy(systemRef, struc.positions, labels=forceLabelsRef) os.mkdir('coupled') os.chdir('coupled') #Do NVT simulation stateFileNVT, stateNVT = doSimNVT(top, systemRef, integratorRef, platform, prop, temperature, pos=struc.positions) #And do NPT simulation using state information from NVT stateFileNPT, stateNPT = doSimNPT(top, systemRef, integratorRef, platform, prop, temperature, scalexy=False, state=stateFileNVT) #Now perform dynamics simulation to get dynamics - this is defined here, NOT in openmm_surface_affinities_lib.py numShellWaters, dipoleCosAng, timePoints = doSimDynamics( top, systemRef, integratorRef, platform, prop, temperature, scalexy=False, coupled=True, state=stateFileNPT) #Finally, want to now save the water residency over time and then also fit to exponential decay np.savetxt( "shell_watCounts_coupled.txt", np.hstack((np.array([timePoints]).T, numShellWaters)), header="Time (ps) Number waters in the 1st and 2nd solvation shells") opt1, pcov1 = optimize.curve_fit( normalExponential, timePoints, numShellWaters[:, 0] / numShellWaters[0, 0]) decayTime1 = opt1[1] opt2, pcov2 = optimize.curve_fit( normalExponential, timePoints, numShellWaters[:, 1] / numShellWaters[0, 1]) decayTime2 = opt2[1] print("\nIn the fully coupled ensemble:") print("\tWater residency correlation time for 1st shell waters: %f" % decayTime1) print("\tWater residency correlation time for 2nd shell waters: %f" % decayTime2) #Finally, want to now save the dipoles over time and then also fit to stretched exponential np.savetxt( "rotational_timeCorr_coupled.txt", np.hstack((np.array([timePoints]).T, dipoleCosAng)), header= "Time (ps) Cos(angle) between starting dipole and dipole for 1st and 2nd solvation shells" ) opt1, pcov1 = optimize.curve_fit(stretchedExponential, timePoints, dipoleCosAng[:, 0]) decayTime1 = opt1[1] opt2, pcov2 = optimize.curve_fit(stretchedExponential, timePoints, dipoleCosAng[:, 1]) decayTime2 = opt2[1] print("\tRotational correlation time for 1st shell waters: %f" % decayTime1) print("\tRotational correlation time for 2nd shell waters: %f" % decayTime2) os.chdir('../') #Next simulate with decoupled state, but do same analysis #At least this way the volumes considered will be similar SoftCoreForce.setGlobalParameterDefaultValue(0, 0.0) SoluteCoulForce.setGlobalParameterDefaultValue(0, 0.0) for k, ind in enumerate(allSoluteIndices): [charge, sig, eps] = NBForce.getParticleParameters(ind) NBForce.setParticleParameters(ind, alchemicalCharges[k] * 0.0, sig, eps) forceLabelsRef = getForceLabels(systemRef) decompEnergy(systemRef, struc.positions, labels=forceLabelsRef) os.mkdir('decoupled') os.chdir('decoupled') #Do NVT simulation stateFileNVT, stateNVT = doSimNVT(top, systemRef, integratorRef, platform, prop, temperature, pos=struc.positions) #And do NPT simulation using state information from NVT stateFileNPT, stateNPT = doSimNPT(top, systemRef, integratorRef, platform, prop, temperature, scalexy=False, state=stateFileNVT) #Now perform dynamics simulation to get dynamics - this is defined here, NOT in openmm_surface_affinities_lib.py numShellWaters, dipoleCosAng, timePoints = doSimDynamics( top, systemRef, integratorRef, platform, prop, temperature, scalexy=False, coupled=False, state=stateFileNPT) #Finally, want to now save the water residency over time and then also fit to exponential decay np.savetxt( "shell_watCounts_decoupled.txt", np.hstack((np.array([timePoints]).T, numShellWaters)), header="Time (ps) Number waters in the 1st and 2nd solvation shells") opt1, pcov1 = optimize.curve_fit( normalExponential, timePoints, numShellWaters[:, 0] / numShellWaters[0, 0]) decayTime1 = opt1[1] opt2, pcov2 = optimize.curve_fit( normalExponential, timePoints, numShellWaters[:, 1] / numShellWaters[0, 1]) decayTime2 = opt2[1] print("\nIn the perfectly decoupled ensemble:") print("\tWater residency correlation time for 1st shell waters: %f" % decayTime1) print("\tWater residency correlation time for 2nd shell waters: %f" % decayTime2) #Finally, want to now save the dipoles over time and then also fit to stretched exponential np.savetxt( "rotational_timeCorr_decoupled.txt", np.hstack((np.array([timePoints]).T, dipoleCosAng)), header= "Time (ps) Cos(angle) between starting dipole and dipole for 1st and 2nd solvation shells" ) opt1, pcov1 = optimize.curve_fit(stretchedExponential, timePoints, dipoleCosAng[:, 0]) decayTime1 = opt1[1] opt2, pcov2 = optimize.curve_fit(stretchedExponential, timePoints, dipoleCosAng[:, 1]) decayTime2 = opt2[1] print("\tRotational correlation time for 1st shell waters: %f" % decayTime1) print("\tRotational correlation time for 2nd shell waters: %f" % decayTime2) os.chdir('../')
def add_interactions(self, state: interfaces.IState, system: mm.System, topology: app.Topology) -> mm.System: # The approach we use is based on # Habeck, Nilges, Rieping, J. Biomol. NMR., 2007, 135-144. # # Rather than solving for the exact alignment tensor # every step, we sample from a distribution of alignment # tensors. # # We encode the five components of the alignment tensor in # the positions of two dummy atoms relative to the center # of mass. The value of kappa should be scaled so that the # components of the alignment tensor are approximately unity. # # There is a restraint on the z-component of the seocnd dummy # particle to ensure that it does not diffuse off to ininity, # which could cause precision issues. if self.active: rdc_force = mm.CustomCentroidBondForce( 5, "Erest + z_scaler*Ez;" "Erest = (1 - step(dev - quadcut)) * quad + step(dev - quadcut) * linear;" "linear = 0.5 * k_rdc * quadcut^2 + k_rdc * quadcut * (dev - quadcut);" "quad = 0.5 * k_rdc * dev^2;" "dev = max(0, abs(d_obs - dcalc) - flat);" "dcalc=2/3 * kappa_rdc/r^5 * (s1*(rx^2-ry^2) + s2*(3*rz^2-r^2) + s3*2*rx*ry + s4*2*rx*rz + s5*2*ry*rz);" "r=distance(g4, g5);" "rx=x4-x5;" "ry=y4-y5;" "rz=z4-z5;" "s1=x2-x1;" "s2=y2-y1;" "s3=z2-z1;" "s4=x3-x1;" "s5=y3-y1;" "Ez=(z3-z1)^2;", ) rdc_force.addPerBondParameter("d_obs") rdc_force.addPerBondParameter("kappa_rdc") rdc_force.addPerBondParameter("k_rdc") rdc_force.addPerBondParameter("flat") rdc_force.addPerBondParameter("quadcut") rdc_force.addPerBondParameter("z_scaler") for experiment in self.expt_dict: # find the set of all atoms involved in this experiment com_ind = set() for r in self.expt_dict[experiment]: com_ind.add(r.atom_index_1) com_ind.add(r.atom_index_2) # add groups for the COM and dummy particles s1 = self.expt_dict[experiment][0].s1_index s2 = self.expt_dict[experiment][0].s2_index g1 = rdc_force.addGroup(list(com_ind)) g2 = rdc_force.addGroup([s1]) g3 = rdc_force.addGroup([s2]) # add non-bonded exclusions between dummy particles and all other atoms nb_forces = [ f for f in system.getForces() if isinstance(f, mm.NonbondedForce) or isinstance(f, mm.CustomNonbondedForce) ] for nb_force in nb_forces: n_parts = nb_force.getNumParticles() for i in range(n_parts): if isinstance(nb_force, mm.NonbondedForce): if i != s1: nb_force.addException(i, s1, 0.0, 0.0, 0.0, replace=True) if i != s2: nb_force.addException(i, s2, 0.0, 0.0, 0.0, replace=True) else: if i != s1: nb_force.addExclusion(i, s1) if i != s2: nb_force.addExclusion(i, s2) for r in self.expt_dict[experiment]: # add groups for the atoms involved in the RDC g4 = rdc_force.addGroup([r.atom_index_1]) g5 = rdc_force.addGroup([r.atom_index_2]) rdc_force.addBond( [g1, g2, g3, g4, g5], [ r.d_obs, r.kappa, 0.0, r.tolerance, r.quadratic_cut, 0, ], # z_scaler initial value shouldn't matter ) system.addForce(rdc_force) self.force = rdc_force return system
def main(args): #Get the structure, topology, and trajectory files from the command line #ParmEd accepts a wide range of file types (Amber, GROMACS, CHARMM, OpenMM... but not LAMMPS) try: topFile = args[0] strucFile = args[1] trajFile = args[2] except IndexError: print( "Specify topology, structure, and trajectory files from the command line." ) print(Usage) sys.exit(2) #And also allow user to specify start frame, but default to zero if no input try: startFrame = int(args[3]) except IndexError: startFrame = 0 #And get information on whether or not to use a restraint try: boolstr = args[4] if boolstr.lower() == 'true' or boolstr.lower() == 'yes': restraintBool = True else: restraintBool = False except IndexError: restraintBool = False print("Using topology file: %s" % topFile) print("Using structure file: %s" % strucFile) print("Using trajectory file: %s" % trajFile) print("\nSetting up system...") #Load in the files for initial simulations top = pmd.load_file(topFile) struc = pmd.load_file(strucFile) #Transfer unit cell information to topology object top.box = struc.box[:] #Set up some global features to use in all simulations temperature = 298.15 * u.kelvin #Define the platform (i.e. hardware and drivers) to use for running the simulation #This can be CUDA, OpenCL, CPU, or Reference #CUDA is for NVIDIA GPUs #OpenCL is for CPUs or GPUs, but must be used for old CPUs (not SSE4.1 compatible) #CPU only allows single precision (CUDA and OpenCL allow single, mixed, or double) #Reference is a clear, stable reference for other code development and is very slow, using double precision by default platform = mm.Platform.getPlatformByName('CUDA') prop = { #'Threads': '1', #number of threads for CPU - all definitions must be strings (I think) 'Precision': 'mixed', #for CUDA or OpenCL, select the precision (single, mixed, or double) 'DeviceIndex': '0', #selects which GPUs to use - set this to zero if using CUDA_VISIBLE_DEVICES 'DeterministicForces': 'True' #Makes sure forces with CUDA and PME are deterministic } #Create the OpenMM system that can be used as a reference systemRef = top.createSystem( nonbondedMethod=app. PME, #Uses PME for long-range electrostatics, simple cut-off for LJ nonbondedCutoff=12.0 * u.angstroms, #Defines cut-off for non-bonded interactions rigidWater=True, #Use rigid water molecules constraints=app.HBonds, #Constrains all bonds involving hydrogens flexibleConstraints= False, #Whether to include energies for constrained DOFs removeCMMotion= True, #Whether or not to remove COM motion (don't want to if part of system frozen) ) #Set up the integrator to use as a reference integratorRef = mm.LangevinIntegrator( temperature, #Temperature for Langevin 1.0 / u.picoseconds, #Friction coefficient 2.0 * u.femtoseconds, #Integration timestep ) integratorRef.setConstraintTolerance(1.0E-08) #Get solute atoms and solute heavy atoms separately soluteIndices = [] heavyIndices = [] for res in top.residues: if res.name not in ['OTM', 'CTM', 'STM', 'NTM', 'SOL']: for atom in res.atoms: soluteIndices.append(atom.idx) if 'H' not in atom.name[0]: heavyIndices.append(atom.idx) #If working with expanded ensemble simulation of solute near the interface, need to include restraint to keep close if restraintBool: #Also get surface SU atoms and surface CU atoms at top and bottom of surface surfIndices = [] for atom in top.atoms: if atom.type == 'SU': surfIndices.append(atom.idx) print("\nSolute indices: %s" % str(soluteIndices)) print("Solute heavy atom indices: %s" % str(heavyIndices)) print("Surface SU atom indices: %s" % str(surfIndices)) #Will now add a custom bonded force between heavy atoms of each solute and surface SU atoms #Should be in units of kJ/mol*nm^2, but should check this #Also, note that here we are using a flat-bottom restraint to keep close to surface #AND to keep from penetrating into surface when it's in the decoupled state refZlo = 1.4 * u.nanometer #in nm, the distance between the SU atoms and the solute centroid refZhi = 1.7 * u.nanometer restraintExpression = '0.5*k*step(refZlo - (z2 - z1))*(((z2 - z1) - refZlo)^2)' restraintExpression += '+ 0.5*k*step((z2 - z1) - refZhi)*(((z2 - z1) - refZhi)^2)' restraintForce = mm.CustomCentroidBondForce(2, restraintExpression) restraintForce.addPerBondParameter('k') restraintForce.addPerBondParameter('refZlo') restraintForce.addPerBondParameter('refZhi') restraintForce.addGroup(surfIndices, np.ones( len(surfIndices))) #Don't weight with masses #To assign flat-bottom restraint correctly, need to know if each solute is above or below interface #Will need surface z-positions for this suZpos = np.average(struc.coordinates[surfIndices, 2]) restraintForce.addGroup(heavyIndices, np.ones(len(heavyIndices))) solZpos = np.average(struc.coordinates[heavyIndices, 2]) if (solZpos - suZpos) > 0: restraintForce.addBond([0, 1], [10000.0, refZlo, refZhi]) else: #A little confusing, but have to negate and switch for when z2-z1 is always going to be negative restraintForce.addBond([0, 1], [10000.0, -refZhi, -refZlo]) systemRef.addForce(restraintForce) #And define lambda states of interest lambdaVec = np.array( #electrostatic lambda - 1.0 is fully interacting, 0.0 is non-interacting [ [ 1.00, 0.75, 0.50, 0.25, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00 ], #LJ lambdas - 1.0 is fully interacting, 0.0 is non-interacting [ 1.00, 1.00, 1.00, 1.00, 1.00, 0.90, 0.80, 0.70, 0.60, 0.50, 0.40, 0.35, 0.30, 0.25, 0.20, 0.15, 0.10, 0.05, 0.00 ] ]) #We need to add a custom non-bonded force for the solute being alchemically changed #Will be helpful to have handle on non-bonded force handling LJ and coulombic interactions NBForce = None for frc in systemRef.getForces(): if (isinstance(frc, mm.NonbondedForce)): NBForce = frc #Turn off dispersion correction since have interface NBForce.setUseDispersionCorrection(False) #Separate out alchemical and regular particles using set objects alchemicalParticles = set(soluteIndices) chemicalParticles = set(range( systemRef.getNumParticles())) - alchemicalParticles #Define the soft-core function for turning on/off LJ interactions #In energy expressions for CustomNonbondedForce, r is a special variable and refers to the distance between particles #All other variables must be defined somewhere in the function. #The exception are variables like sigma1 and sigma2. #It is understood that a parameter will be added called 'sigma' and that the '1' and '2' are to specify the combining rule. #Have also added parameter to switch the soft-core interaction to a WCA potential softCoreFunctionWCA = '(step(x-0.5))*(4.0*lambdaLJ*epsilon*x*(x-1.0) + (1.0-lambdaWCA)*lambdaLJ*epsilon) ' softCoreFunctionWCA += '+ (1.0 - step(x-0.5))*lambdaWCA*(4.0*lambdaLJ*epsilon*x*(x-1.0));' softCoreFunctionWCA += 'x = (1.0/reff_sterics);' softCoreFunctionWCA += 'reff_sterics = (0.5*(1.0-lambdaLJ) + ((r/sigma)^6));' softCoreFunctionWCA += 'sigma=0.5*(sigma1+sigma2); epsilon = sqrt(epsilon1*epsilon2)' #Define the system force for this function and its parameters SoftCoreForceWCA = mm.CustomNonbondedForce(softCoreFunctionWCA) SoftCoreForceWCA.addGlobalParameter( 'lambdaLJ', 1.0 ) #Throughout, should follow convention that lambdaLJ=1.0 is fully-interacting state SoftCoreForceWCA.addGlobalParameter( 'lambdaWCA', 1.0) #When 1, attractions included; setting to 0 turns off attractions SoftCoreForceWCA.addPerParticleParameter('sigma') SoftCoreForceWCA.addPerParticleParameter('epsilon') #Will turn off electrostatics completely in the original non-bonded force #In the end-state, only want electrostatics inside the alchemical molecule #To do this, just turn ON a custom force as we turn OFF electrostatics in the original force ONE_4PI_EPS0 = 138.935456 #in kJ/mol nm/e^2 soluteCoulFunction = '(1.0-(lambdaQ^2))*ONE_4PI_EPS0*charge/r;' soluteCoulFunction += 'ONE_4PI_EPS0 = %.16e;' % (ONE_4PI_EPS0) soluteCoulFunction += 'charge = charge1*charge2' SoluteCoulForce = mm.CustomNonbondedForce(soluteCoulFunction) #Note this lambdaQ will be different than for soft core (it's also named differently, which is CRITICAL) #This lambdaQ corresponds to the lambda that scales the charges to zero #To turn on this custom force at the same rate, need to multiply by (1.0-lambdaQ**2), which we do SoluteCoulForce.addGlobalParameter('lambdaQ', 1.0) SoluteCoulForce.addPerParticleParameter('charge') #Also create custom force for intramolecular alchemical LJ interactions #Could include with electrostatics, but nice to break up #We could also do this with a separate NonbondedForce object, but it would be a little more work, actually soluteLJFunction = '4.0*epsilon*x*(x-1.0); x = (sigma/r)^6;' soluteLJFunction += 'sigma=0.5*(sigma1+sigma2); epsilon=sqrt(epsilon1*epsilon2)' SoluteLJForce = mm.CustomNonbondedForce(soluteLJFunction) SoluteLJForce.addPerParticleParameter('sigma') SoluteLJForce.addPerParticleParameter('epsilon') #Loop over all particles and add to custom forces #As we go, will also collect full charges on the solute particles #AND we will set up the solute-solute interaction forces alchemicalCharges = [[0]] * len(soluteIndices) for ind in range(systemRef.getNumParticles()): #Get current parameters in non-bonded force [charge, sigma, epsilon] = NBForce.getParticleParameters(ind) #Make sure that sigma is not set to zero! Fine for some ways of writing LJ energy, but NOT OK for soft-core! if sigma / u.nanometer == 0.0: newsigma = 0.3 * u.nanometer #This 0.3 is what's used by GROMACS as a default value for sc-sigma else: newsigma = sigma #Add the particle to the soft-core force (do for ALL particles) SoftCoreForceWCA.addParticle([newsigma, epsilon]) #Also add the particle to the solute only forces SoluteCoulForce.addParticle([charge]) SoluteLJForce.addParticle([sigma, epsilon]) #If the particle is in the alchemical molecule, need to set it's LJ interactions to zero in original force if ind in soluteIndices: NBForce.setParticleParameters(ind, charge, sigma, epsilon * 0.0) #And keep track of full charge so we can scale it right by lambda alchemicalCharges[soluteIndices.index(ind)] = charge #Now we need to handle exceptions carefully for ind in range(NBForce.getNumExceptions()): [p1, p2, excCharge, excSig, excEps] = NBForce.getExceptionParameters(ind) #For consistency, must add exclusions where we have exceptions for custom forces SoftCoreForceWCA.addExclusion(p1, p2) SoluteCoulForce.addExclusion(p1, p2) SoluteLJForce.addExclusion(p1, p2) #Only compute interactions between the alchemical and other particles for the soft-core force SoftCoreForceWCA.addInteractionGroup(alchemicalParticles, chemicalParticles) #And only compute alchemical/alchemical interactions for other custom forces SoluteCoulForce.addInteractionGroup(alchemicalParticles, alchemicalParticles) SoluteLJForce.addInteractionGroup(alchemicalParticles, alchemicalParticles) #Set other soft-core parameters as needed SoftCoreForceWCA.setCutoffDistance(12.0 * u.angstroms) SoftCoreForceWCA.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic) SoftCoreForceWCA.setUseLongRangeCorrection(False) systemRef.addForce(SoftCoreForceWCA) #Set other parameters as needed - note that for the solute force would like to set no cutoff #However, OpenMM won't allow a bunch of potentials with cutoffs then one without... #So as long as the solute is smaller than the cut-off, won't have any problems! SoluteCoulForce.setCutoffDistance(12.0 * u.angstroms) SoluteCoulForce.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic) SoluteCoulForce.setUseLongRangeCorrection(False) systemRef.addForce(SoluteCoulForce) SoluteLJForce.setCutoffDistance(12.0 * u.angstroms) SoluteLJForce.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic) SoluteLJForce.setUseLongRangeCorrection(False) systemRef.addForce(SoluteLJForce) #Need to add integrator and context in order to evaluate potential energies #Integrator is arbitrary because won't use it integrator = mm.VerletIntegrator(1.0 * u.femtoseconds) context = mm.Context(systemRef, integrator, platform, prop) ########################################################################## print("\nStarting analysis...") kBT = u.AVOGADRO_CONSTANT_NA * u.BOLTZMANN_CONSTANT_kB * temperature #Set up arrays to hold potential energies #First row will be coupled, then no electrostatics, then no electrostatics with WCA, then decoupled allU = np.array([[]] * 4).T #We've now set everything up like we're going to run a simulation #But now we will use pytraj to load a trajectory to get coordinates #With those coordinates, we will evaluate the energies we want #Just need to figure out if we have a surface or a bulk system trajFiles = glob.glob('Quad*/%s' % trajFile) if len(trajFiles) == 0: trajFiles = [trajFile] print("Using following trajectory files: %s" % str(trajFiles)) for aFile in trajFiles: trajtop = copy.deepcopy(top) trajtop.rb_torsions = pmd.TrackedList( []) #Necessary for SAM systems so doesn't break pytraj trajtop = pt.load_parmed(trajtop, traj=False) traj = pt.iterload(aFile, top=trajtop, frame_slice=(startFrame, -1)) thisAllU = np.zeros((len(traj), 4)) #Loop over the lambda states of interest, looping over whole trajectory each time for i, lstate in enumerate([ [1.0, 1.0, 1.0], #Fully coupled [1.0, 1.0, 0.0], #Charge turned off [1.0, 0.0, 0.0], #Charged turned off, attractions turned off (so WCA) [0.0, 1.0, 0.0] ]): #Decoupled (WCA still included, though doesn't matter) #Set the lambda state context.setParameter('lambdaLJ', lstate[0]) context.setParameter('lambdaWCA', lstate[1]) context.setParameter('lambdaQ', lstate[2]) for k, ind in enumerate(soluteIndices): [charge, sig, eps] = NBForce.getParticleParameters(ind) NBForce.setParticleParameters(ind, alchemicalCharges[k] * lstate[2], sig, eps) NBForce.updateParametersInContext(context) #And loop over trajectory for t, frame in enumerate(traj): thisbox = np.array(frame.box.values[:3]) context.setPeriodicBoxVectors( np.array([thisbox[0], 0.0, 0.0]) * u.angstrom, np.array([0.0, thisbox[1], 0.0]) * u.angstrom, np.array([0.0, 0.0, thisbox[2]]) * u.angstrom) thispos = np.array(frame.xyz) * u.angstrom context.setPositions(thispos) thisAllU[t, i] = context.getState( getEnergy=True).getPotentialEnergy() / kBT #Add this trajectory information allU = np.vstack((allU, thisAllU)) #And that should be it, just need to save files and print information #avgUq = np.average(allU[0,:] - allU[1,:]) #stdUq = np.std(allU[0,:] - allU[1,:], ddof=1) #avgUlj = np.average(allU[1,:] - allU[2,:]) #stdUlj = np.std(allU[1,:] - allU[2,:], ddof=1) #print("\nAverage solute-water electrostatic potential energy: %f +/- %f"%(avgUq, stdUq)) #print("Average solute-water LJ potential energy: %f +/- %f"%(avgUlj, stdUlj)) np.savetxt( 'pot_energy_decomp.txt', allU, header= 'U_coupled (kBT) U_noQ (kBT) U_noQ_WCA (kBT) U_decoupled (kBT) ' ) #Print some meaningful information #Just make sure we do so as accurately as possible using alchemical information alchFile = glob.glob('alchemical_U*.txt')[0] print('Using alchemical information file: %s' % alchFile) printInfo(allU, mbarFile='mbar_object.pkl', alchFile=alchFile)
def main(args): #Get the structure and topology files from the command line #ParmEd accepts a wide range of file types (Amber, GROMACS, CHARMM, OpenMM... but not LAMMPS) try: topFile = args[0] strucFile = args[1] except IndexError: print("Specify topology and structure files from the command line.") sys.exit(2) print("Using topology file: %s" % topFile) print("Using structure file: %s" % strucFile) print("\nSetting up system...") #Load in the files for initial simulations top = pmd.load_file(topFile) struc = pmd.load_file(strucFile) #Transfer unit cell information to topology object top.box = struc.box[:] #Set up some global features to use in all simulations temperature = 298.15 * u.kelvin #Define the platform (i.e. hardware and drivers) to use for running the simulation #This can be CUDA, OpenCL, CPU, or Reference #CUDA is for NVIDIA GPUs #OpenCL is for CPUs or GPUs, but must be used for old CPUs (not SSE4.1 compatible) #CPU only allows single precision (CUDA and OpenCL allow single, mixed, or double) #Reference is a clear, stable reference for other code development and is very slow, using double precision by default platform = mm.Platform.getPlatformByName('CUDA') prop = { #'Threads': '2', #number of threads for CPU - all definitions must be strings (I think) 'Precision': 'mixed', #for CUDA or OpenCL, select the precision (single, mixed, or double) 'DeviceIndex': '0', #selects which GPUs to use - set this to zero if using CUDA_VISIBLE_DEVICES 'DeterministicForces': 'True' #Makes sure forces with CUDA and PME are deterministic } #Create the OpenMM system that can be used as a reference systemRef = top.createSystem( nonbondedMethod=app. PME, #Uses PME for long-range electrostatics, simple cut-off for LJ nonbondedCutoff=12.0 * u.angstroms, #Defines cut-off for non-bonded interactions rigidWater=True, #Use rigid water molecules constraints=app.HBonds, #Constrains all bonds involving hydrogens flexibleConstraints= False, #Whether to include energies for constrained DOFs removeCMMotion= False, #Whether or not to remove COM motion (don't want to if part of system frozen) ) #Set up the integrator to use as a reference integratorRef = mm.LangevinIntegrator( temperature, #Temperature for Langevin 1.0 / u.picoseconds, #Friction coefficient 2.0 * u.femtoseconds, #Integration timestep ) integratorRef.setConstraintTolerance(1.0E-08) #To freeze atoms, set mass to zero (does not apply to virtual sites, termed "extra particles" in OpenMM) #Here assume (correctly, I think) that the topology indices for atoms correspond to those in the system for i, atom in enumerate(top.atoms): if atom.type in ('SU'): #, 'CU', 'CUO'): systemRef.setParticleMass(i, 0 * u.dalton) #Get solute atoms and solute heavy atoms separately #Do this for as many solutes as we have so get list for each soluteIndices = [] heavyIndices = [] for res in top.residues: if res.name not in ['OTM', 'CTM', 'STM', 'NTM', 'SOL']: thissolinds = [] thisheavyinds = [] for atom in res.atoms: thissolinds.append(atom.idx) if 'H' not in atom.name[0]: thisheavyinds.append(atom.idx) soluteIndices.append(thissolinds) heavyIndices.append(thisheavyinds) #For convenience, also create flattened version of soluteIndices allSoluteIndices = [] for inds in soluteIndices: allSoluteIndices += inds #Also get surface SU atoms and surface CU atoms at top and bottom of surface surfIndices = [] for atom in top.atoms: if atom.type == 'SU': surfIndices.append(atom.idx) print("\nSolute indices: %s" % str(soluteIndices)) print("(all together - %s)" % str(allSoluteIndices)) print("Solute heavy atom indices: %s" % str(heavyIndices)) print("Surface SU atom indices: %s" % str(surfIndices)) #Will now add a custom bonded force between heavy atoms of each solute and surface SU atoms #Should be in units of kJ/mol*nm^2, but should check this #Also, note that here we are using a flat-bottom restraint to keep close to surface #AND to keep from penetrating into surface when it's in the decoupled state refZlo = 1.4 * u.nanometer #in nm, the distance between the SU atoms and the solute centroid refZhi = 1.7 * u.nanometer restraintExpression = '0.5*k*step(refZlo - (z2 - z1))*(((z2 - z1) - refZlo)^2)' restraintExpression += '+ 0.5*k*step((z2 - z1) - refZhi)*(((z2 - z1) - refZhi)^2)' restraintForce = mm.CustomCentroidBondForce(2, restraintExpression) restraintForce.addPerBondParameter('k') restraintForce.addPerBondParameter('refZlo') restraintForce.addPerBondParameter('refZhi') restraintForce.addGroup(surfIndices, np.ones( len(surfIndices))) #Don't weight with masses #To assign flat-bottom restraint correctly, need to know if each solute is above or below interface #Will need surface z-positions for this suZpos = np.average(struc.coordinates[surfIndices, 2]) for i in range(len(heavyIndices)): restraintForce.addGroup(heavyIndices[i], np.ones(len(heavyIndices[i]))) solZpos = np.average(struc.coordinates[heavyIndices[i], 2]) if (solZpos - suZpos) > 0: restraintForce.addBond([0, i + 1], [10000.0, refZlo, refZhi]) else: #A little confusing, but have to negate and switch for when z2-z1 is always going to be negative restraintForce.addBond([0, i + 1], [10000.0, -refZhi, -refZlo]) systemRef.addForce(restraintForce) #We also will need to force the solute to sample the entire surface #We will do this with flat-bottom restraints in x and y #But NOT on the centroid, because this is hard with CustomExternalForce #Instead, just apply it to the first heavy atom of each solute #Won't worry about keeping each solute in the same region of the surface on both faces... #Actually better if define regions on both faces differently initRefX = 0.25 * top.box[ 0] * u.angstrom #Used parmed to load, and that program uses angstroms initRefY = 0.25 * top.box[1] * u.angstrom refDistX = 0.25 * top.box[0] * u.angstrom refDistY = 0.25 * top.box[1] * u.angstrom print('Default X reference distance: %s' % str(initRefX)) print('Default Y reference distance: %s' % str(initRefY)) restraintExpressionXY = '0.5*k*step(periodicdistance(x,0,0,refX,0,0) - distX)*((periodicdistance(x,0,0,refX,0,0) - distX)^2)' restraintExpressionXY += '+ 0.5*k*step(periodicdistance(0,y,0,0,refY,0) - distY)*((periodicdistance(0,y,0,0,refY,0) - distY)^2)' restraintXYForce = mm.CustomExternalForce(restraintExpressionXY) restraintXYForce.addPerParticleParameter('k') restraintXYForce.addPerParticleParameter('distX') restraintXYForce.addPerParticleParameter('distY') restraintXYForce.addGlobalParameter('refX', initRefX) restraintXYForce.addGlobalParameter('refY', initRefY) for i in range(len(heavyIndices)): restraintXYForce.addParticle( heavyIndices[i][0], [1000.0, refDistX, refDistY]) #k in units kJ/mol*nm^2 systemRef.addForce(restraintXYForce) #JUST for acetic acid, will also need to add lambda states in order to soften torsions around the C-OH bond #Will base this on the work of Paluch, et al. 2011 #First soften the torsions in the coupled state, then turn them on in the decoupled state #Defining lambda states here #Below will define a custom torsion force so can globally modify dihedrals of interest alchemically lambdaVec = np.array( #electrostatic lambda - 1.0 is fully interacting, 0.0 is non-interacting [ [ 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 0.75, 0.50, 0.25, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00 ], #LJ lambdas - 1.0 is fully interacting, 0.0 is non-interacting [ 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 0.90, 0.80, 0.70, 0.60, 0.50, 0.40, 0.35, 0.30, 0.25, 0.20, 0.15, 0.10, 0.05, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00 ], #Torsion lambdas - 1.0 is fully on, softening from there [ 1.00, 0.90, 0.70, 0.30, 0.10, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.10, 0.30, 0.70, 0.90, 1.00 ] ]) #We need to add a custom non-bonded force for the solute being alchemically changed #Will be helpful to have handle on non-bonded force handling LJ and coulombic interactions NBForce = None for frc in systemRef.getForces(): if (isinstance(frc, mm.NonbondedForce)): NBForce = frc #Turn off dispersion correction since have interface NBForce.setUseDispersionCorrection(False) forceLabelsRef = getForceLabels(systemRef) decompEnergy(systemRef, struc.positions, labels=forceLabelsRef, verbose=True) #JUST do the below for acetic acid! #Otherwise will modify all torsions involving hydroxyls ########################### Starting some torsion modifications ########################### TorsionForce = None for frc in systemRef.getForces(): if (isinstance(frc, mm.PeriodicTorsionForce)): TorsionForce = frc #Create custom torsion for those we want to soften alchTorsionFunction = "lambdaTorsion*k*(1+cos(per*theta-theta0))" AlchTorsionForce = mm.CustomTorsionForce(alchTorsionFunction) AlchTorsionForce.addGlobalParameter('lambdaTorsion', 1.0) AlchTorsionForce.addPerTorsionParameter('per') AlchTorsionForce.addPerTorsionParameter('theta0') AlchTorsionForce.addPerTorsionParameter('k') #Now need to find specific dihedrals we want to modify for ind in range(TorsionForce.getNumTorsions()): p1, p2, p3, p4, periodicity, phase, kval = TorsionForce.getTorsionParameters( ind) thisAtomNames = [top.atoms[x].name for x in [p1, p2, p3, p4]] if ((set([p1, p2, p3, p4]).issubset(allSoluteIndices)) and (set(['H', 'O']).issubset(thisAtomNames))): AlchTorsionForce.addTorsion(p1, p2, p3, p4, [periodicity, phase, kval]) #Don't want to duplicate TorsionForce.setTorsionParameters(ind, p1, p2, p3, p4, periodicity, phase, kval * 0.0) #Add custom torsion force to our system systemRef.addForce(AlchTorsionForce) ########################### Done with torsion modification bit ########################### #Separate out alchemical and regular particles using set objects alchemicalParticles = set(allSoluteIndices) chemicalParticles = set(range( systemRef.getNumParticles())) - alchemicalParticles #Define the soft-core function for turning on/off LJ interactions #In energy expressions for CustomNonbondedForce, r is a special variable and refers to the distance between particles #All other variables must be defined somewhere in the function. #The exception are variables like sigma1 and sigma2. #It is understood that a parameter will be added called 'sigma' and that the '1' and '2' are to specify the combining rule. softCoreFunction = '4.0*lambdaLJ*epsilon*x*(x-1.0); x = (1.0/reff_sterics);' softCoreFunction += 'reff_sterics = (0.5*(1.0-lambdaLJ) + ((r/sigma)^6));' softCoreFunction += 'sigma=0.5*(sigma1+sigma2); epsilon = sqrt(epsilon1*epsilon2)' #Define the system force for this function and its parameters SoftCoreForce = mm.CustomNonbondedForce(softCoreFunction) SoftCoreForce.addGlobalParameter( 'lambdaLJ', 1.0 ) #Throughout, should follow convention that lambdaLJ=1.0 is fully-interacting state SoftCoreForce.addPerParticleParameter('sigma') SoftCoreForce.addPerParticleParameter('epsilon') #Will turn off electrostatics completely in the original non-bonded force #In the end-state, only want electrostatics inside the alchemical molecule #To do this, just turn ON a custom force as we turn OFF electrostatics in the original force ONE_4PI_EPS0 = 138.935456 #in kJ/mol nm/e^2 soluteCoulFunction = '(1.0-(lambdaQ^2))*ONE_4PI_EPS0*charge/r;' soluteCoulFunction += 'ONE_4PI_EPS0 = %.16e;' % (ONE_4PI_EPS0) soluteCoulFunction += 'charge = charge1*charge2' SoluteCoulForce = mm.CustomNonbondedForce(soluteCoulFunction) #Note this lambdaQ will be different than for soft core (it's also named differently, which is CRITICAL) #This lambdaQ corresponds to the lambda that scales the charges to zero #To turn on this custom force at the same rate, need to multiply by (1.0-lambdaQ**2), which we do SoluteCoulForce.addGlobalParameter('lambdaQ', 1.0) SoluteCoulForce.addPerParticleParameter('charge') #Also create custom force for intramolecular alchemical LJ interactions #Could include with electrostatics, but nice to break up #We could also do this with a separate NonbondedForce object, but it would be a little more work, actually soluteLJFunction = '4.0*epsilon*x*(x-1.0); x = (sigma/r)^6;' soluteLJFunction += 'sigma=0.5*(sigma1+sigma2); epsilon=sqrt(epsilon1*epsilon2)' SoluteLJForce = mm.CustomNonbondedForce(soluteLJFunction) SoluteLJForce.addPerParticleParameter('sigma') SoluteLJForce.addPerParticleParameter('epsilon') #Loop over all particles and add to custom forces #As we go, will also collect full charges on the solute particles #AND we will set up the solute-solute interaction forces alchemicalCharges = [[0]] * len(allSoluteIndices) for ind in range(systemRef.getNumParticles()): #Get current parameters in non-bonded force [charge, sigma, epsilon] = NBForce.getParticleParameters(ind) #Make sure that sigma is not set to zero! Fine for some ways of writing LJ energy, but NOT OK for soft-core! if sigma / u.nanometer == 0.0: newsigma = 0.3 * u.nanometer #This 0.3 is what's used by GROMACS as a default value for sc-sigma else: newsigma = sigma #Add the particle to the soft-core force (do for ALL particles) SoftCoreForce.addParticle([newsigma, epsilon]) #Also add the particle to the solute only forces SoluteCoulForce.addParticle([charge]) SoluteLJForce.addParticle([sigma, epsilon]) #If the particle is in the alchemical molecule, need to set it's LJ interactions to zero in original force if ind in allSoluteIndices: NBForce.setParticleParameters(ind, charge, sigma, epsilon * 0.0) #And keep track of full charge so we can scale it right by lambda alchemicalCharges[allSoluteIndices.index(ind)] = charge #Now we need to handle exceptions carefully for ind in range(NBForce.getNumExceptions()): [p1, p2, excCharge, excSig, excEps] = NBForce.getExceptionParameters(ind) #For consistency, must add exclusions where we have exceptions for custom forces SoftCoreForce.addExclusion(p1, p2) SoluteCoulForce.addExclusion(p1, p2) SoluteLJForce.addExclusion(p1, p2) #Only compute interactions between the alchemical and other particles for the soft-core force SoftCoreForce.addInteractionGroup(alchemicalParticles, chemicalParticles) #And only compute alchemical/alchemical interactions for other custom forces SoluteCoulForce.addInteractionGroup(alchemicalParticles, alchemicalParticles) SoluteLJForce.addInteractionGroup(alchemicalParticles, alchemicalParticles) #Set other soft-core parameters as needed SoftCoreForce.setCutoffDistance(12.0 * u.angstroms) SoftCoreForce.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic) SoftCoreForce.setUseLongRangeCorrection(False) systemRef.addForce(SoftCoreForce) #Set other parameters as needed - note that for the solute force would like to set no cutoff #However, OpenMM won't allow a bunch of potentials with cutoffs then one without... #So as long as the solute is smaller than the cut-off, won't have any problems! SoluteCoulForce.setCutoffDistance(12.0 * u.angstroms) SoluteCoulForce.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic) SoluteCoulForce.setUseLongRangeCorrection(False) systemRef.addForce(SoluteCoulForce) SoluteLJForce.setCutoffDistance(12.0 * u.angstroms) SoluteLJForce.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic) SoluteLJForce.setUseLongRangeCorrection(False) systemRef.addForce(SoluteLJForce) forceLabelsRef = getForceLabels(systemRef) decompEnergy(systemRef, struc.positions, labels=forceLabelsRef) #Run set of simulations for particle restrained in x and y to each quadrant of the surface #For best results, the configuration read in should have the solute right in the center of the surface #Will store the lambda biasing weights as we got to help speed convergence (hopefully) for xfrac in [0.25, 0.75]: for yfrac in [0.25, 0.75]: os.mkdir('Quad_%1.2fX_%1.2fY' % (xfrac, yfrac)) os.chdir('Quad_%1.2fX_%1.2fY' % (xfrac, yfrac)) restraintXYForce.setGlobalParameterDefaultValue( 0, xfrac * top.box[0] * u.angstrom) restraintXYForce.setGlobalParameterDefaultValue( 1, yfrac * top.box[1] * u.angstrom) decompEnergy(systemRef, struc.positions, labels=forceLabelsRef) #Do NVT simulation stateFileNVT, stateNVT = doSimNVT(top, systemRef, integratorRef, platform, prop, temperature, pos=struc.positions) #And do NPT simulation using state information from NVT stateFileNPT, stateNPT = doSimNPT(top, systemRef, integratorRef, platform, prop, temperature, state=stateFileNVT) #And do production run in expanded ensemble! stateFileProd, stateProd, weightsVec = doSimExpanded( top, systemRef, integratorRef, platform, prop, temperature, 0, lambdaVec, allSoluteIndices, alchemicalCharges, state=stateFileNPT, nSteps=20000000, equilSteps=7500000) #Save final simulation configuration in .gro format (mainly for genetic algorithm) finalStruc = copy.deepcopy(struc) boxVecs = np.array(stateProd.getPeriodicBoxVectors().value_in_unit( u.angstrom)) # parmed works with angstroms finalStruc.box = np.array([ boxVecs[0, 0], boxVecs[1, 1], boxVecs[2, 2], 90.0, 90.0, 90.0 ]) finalStruc.coordinates = np.array( stateProd.getPositions().value_in_unit(u.angstrom)) finalStruc.save('prod.gro') os.chdir('../')
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], []) system.addForce(force)