def get_standard_state_correction(self): """ Compute the standard state correction for the arbitrary restraint energy function. Returns ------- DeltaG : float Computed standard-state correction in dimensionless units (kT) Notes ----- Uses analytical approach from [1], but this approach is known to be inexact. """ class Bunch(object): """Make a dict accessible via an object accessor""" def __init__(self, adict): self.__dict__.update(adict) # Retrieve constants for convenience. p = Bunch(self._parameters) kT = self.kT pi = np.pi # Eq 32 of Ref [1] DeltaG = -np.log( \ (8. * pi**2 * V0) / (p.r_aA0**2 * unit.sin(p.theta_A0) * unit.sin(p.theta_B0)) \ * unit.sqrt(p.K_r * p.K_thetaA * p.K_thetaB * p.K_phiA * p.K_phiB * p.K_phiC) / (2 * pi * kT)**3 \ ) # Return standard state correction (in kT). return DeltaG
def testUnitMathModule(self): """ Tests the unit_math functions on Quantity objects """ self.assertEqual(u.sqrt(1.0*u.kilogram*u.joule), 1.0*u.kilogram*u.meter/u.second) self.assertEqual(u.sqrt(1.0*u.kilogram*u.calorie), math.sqrt(4.184)*u.kilogram*u.meter/u.second) self.assertEqual(u.sqrt(9), 3) # Test on a scalar self.assertEqual(u.sin(90*u.degrees), 1) self.assertEqual(u.sin(math.pi/2*u.radians), 1) self.assertEqual(u.sin(math.pi/2), 1) self.assertEqual(u.cos(180*u.degrees), -1) self.assertEqual(u.cos(math.pi*u.radians), -1) self.assertEqual(u.cos(math.pi), -1) self.assertAlmostEqual(u.tan(45*u.degrees), 1) self.assertAlmostEqual(u.tan(math.pi/4*u.radians), 1) self.assertAlmostEqual(u.tan(math.pi/4), 1) acos = u.acos(1.0) asin = u.asin(1.0) atan = u.atan(1.0) self.assertTrue(u.is_quantity(acos)) self.assertTrue(u.is_quantity(asin)) self.assertTrue(u.is_quantity(atan)) self.assertEqual(acos.unit, u.radians) self.assertEqual(asin.unit, u.radians) self.assertEqual(atan.unit, u.radians) self.assertEqual(acos.value_in_unit(u.degrees), 0) self.assertEqual(acos / u.radians, 0) self.assertEqual(asin.value_in_unit(u.degrees), 90) self.assertEqual(asin / u.radians, math.pi/2) self.assertAlmostEqual(atan.value_in_unit(u.degrees), 45) self.assertAlmostEqual(atan / u.radians, math.pi/4) # Check some sequence maths seq = [1, 2, 3, 4] * u.meters self.assertEqual(u.sum(seq), 10*u.meters) self.assertEqual(u.dot(seq, seq), (1+4+9+16)*u.meters**2) self.assertEqual(u.norm(seq), math.sqrt(30)*u.meters)
def createSystem(self, nonbondedMethod=ff.NoCutoff, nonbondedCutoff=1.0*u.nanometer, constraints=None, rigidWater=True, implicitSolvent=None, implicitSolventKappa=None, implicitSolventSaltConc=0.0*u.moles/u.liter, temperature=298.15*u.kelvin, soluteDielectric=1.0, solventDielectric=78.5, removeCMMotion=True, hydrogenMass=None, ewaldErrorTolerance=0.0005, flexibleConstraints=True, verbose=False): """ Construct an OpenMM System representing the topology described by the prmtop file. Parameters: - nonbondedMethod (object=NoCutoff) The method to use for nonbonded interactions. Allowed values are NoCutoff, CutoffNonPeriodic, CutoffPeriodic, Ewald, or PME. - nonbondedCutoff (distance=1*nanometer) The cutoff distance to use for nonbonded interactions. - constraints (object=None) Specifies which bonds or angles should be implemented with constraints. Allowed values are None, HBonds, AllBonds, or HAngles. - rigidWater (boolean=True) If true, water molecules will be fully rigid regardless of the value passed for the constraints argument - implicitSolvent (object=None) If not None, the implicit solvent model to use. Allowed values are HCT, OBC1, OBC2, or GBn - implicitSolventKappa (float=None): Debye screening parameter to model salt concentrations in GB solvent. - implicitSolventSaltConc (float=0.0*u.moles/u.liter): Salt concentration for GB simulations. Converted to Debye length `kappa' - temperature (float=298.15*u.kelvin): Temperature used in the salt concentration-to-kappa conversion for GB salt concentration term - soluteDielectric (float=1.0) The solute dielectric constant to use in the implicit solvent model. - solventDielectric (float=78.5) The solvent dielectric constant to use in the implicit solvent model. - removeCMMotion (boolean=True) If true, a CMMotionRemover will be added to the System. - hydrogenMass (mass=None) The mass to use for hydrogen atoms bound to heavy atoms. Any mass added to a hydrogen is subtracted from the heavy atom to keep their total mass the same. - ewaldErrorTolerance (float=0.0005) The error tolerance to use if the nonbonded method is Ewald or PME. - flexibleConstraints (bool=True) Are our constraints flexible or not? - verbose (bool=False) Optionally prints out a running progress report """ # Rebuild the topology file if necessary, and flush the atom property # data to the atom list if self._topology_changed(): self.remake_parm() else: self.atom_list.refresh_data() LJ_radius, LJ_depth = self.openmm_LJ() # Get our LJ parameters LJ_14_radius, LJ_14_depth = self.openmm_14_LJ() # Set the cutoff distance in nanometers cutoff = None if nonbondedMethod is not ff.NoCutoff: cutoff = nonbondedCutoff # Remove units from cutoff if u.is_quantity(cutoff): cutoff = cutoff.value_in_unit(u.nanometers) if nonbondedMethod not in (ff.NoCutoff, ff.CutoffNonPeriodic, ff.CutoffPeriodic, ff.Ewald, ff.PME): raise ValueError('Illegal value for nonbonded method') if self.ptr('ifbox') == 0 and nonbondedMethod in (ff.CutoffPeriodic, ff.Ewald, ff.PME): raise ValueError('Illegal nonbonded method for a ' 'non-periodic system') if implicitSolvent not in (HCT, OBC1, OBC2, GBn, GBn2, None): raise ValueError('Illegal implicit solvent model choice.') if not constraints in (None, ff.HAngles, ff.HBonds, ff.AllBonds): raise ValueError('Illegal constraints choice') # Define conversion factors length_conv = u.angstrom.conversion_factor_to(u.nanometer) _ambfrc = u.kilocalorie_per_mole/(u.angstrom*u.angstrom) _openmmfrc = u.kilojoule_per_mole/(u.nanometer*u.nanometer) bond_frc_conv = _ambfrc.conversion_factor_to(_openmmfrc) _ambfrc = u.kilocalorie_per_mole/(u.radians*u.radians) _openmmfrc = u.kilojoule_per_mole/(u.radians*u.radians) angle_frc_conv = _ambfrc.conversion_factor_to(_openmmfrc) dihe_frc_conv = u.kilocalorie_per_mole.conversion_factor_to( u.kilojoule_per_mole) ene_conv = dihe_frc_conv # Create the system system = mm.System() if verbose: print('Adding particles...') for mass in self.parm_data['MASS']: system.addParticle(mass) # Set up the constraints if verbose and (constraints is not None and not rigidWater): print('Adding constraints...') if constraints in (ff.HBonds, ff.AllBonds, ff.HAngles): for bond in self.bonds_inc_h: system.addConstraint(bond.atom1.starting_index, bond.atom2.starting_index, bond.bond_type.req*length_conv) if constraints in (ff.AllBonds, ff.HAngles): for bond in self.bonds_without_h: system.addConstraint(bond.atom1.starting_index, bond.atom2.starting_index, bond.bond_type.req*length_conv) if rigidWater and constraints is None: for bond in self.bonds_inc_h: if (bond.atom1.residue.resname in WATNAMES and bond.atom2.residue.resname in WATNAMES): system.addConstraint(bond.atom1.starting_index, bond.atom2.starting_index, bond.bond_type.req*length_conv) # Add Bond forces if verbose: print('Adding bonds...') force = mm.HarmonicBondForce() force.setForceGroup(self.BOND_FORCE_GROUP) if flexibleConstraints or (constraints not in (ff.HBonds, ff.AllBonds, ff.HAngles)): for bond in self.bonds_inc_h: force.addBond(bond.atom1.starting_index, bond.atom2.starting_index, bond.bond_type.req*length_conv, 2*bond.bond_type.k*bond_frc_conv) if flexibleConstraints or (constraints not in (ff.AllBonds,ff.HAngles)): for bond in self.bonds_without_h: force.addBond(bond.atom1.starting_index, bond.atom2.starting_index, bond.bond_type.req*length_conv, 2*bond.bond_type.k*bond_frc_conv) system.addForce(force) # Add Angle forces if verbose: print('Adding angles...') force = mm.HarmonicAngleForce() force.setForceGroup(self.ANGLE_FORCE_GROUP) if constraints is ff.HAngles: num_constrained_bonds = system.getNumConstraints() atom_constraints = [[]] * system.getNumParticles() for i in range(num_constrained_bonds): c = system.getConstraintParameters(i) dist = c[2].value_in_unit(u.nanometer) atom_constraints[c[0]].append((c[1], dist)) atom_constraints[c[1]].append((c[0], dist)) for angle in self.angles_inc_h: if constraints is ff.HAngles: a1 = angle.atom1.element a2 = angle.atom2.element a3 = angle.atom3.element nh = int(a1==1) + int(a2==1) + int(a3==1) constrained = (nh >= 2 or (nh == 1 and a2 == 8)) else: constrained = False # no constraints if constrained: l1 = l2 = None for bond in angle.atom2.bonds: if bond.atom1 is angle.atom1 or bond.atom2 is angle.atom1: l1 = bond.bond_type.req * length_conv elif bond.atom1 is angle.atom3 or bond.atom2 is angle.atom3: l2 = bond.bond_type.req * length_conv # Compute the distance between the atoms and add a constraint length = sqrt(l1*l1 + l2*l2 - 2*l1*l2* cos(angle.angle_type.theteq)) system.addConstraint(bond.atom1.starting_index, bond.atom2.starting_index, length) if flexibleConstraints or not constrained: force.addAngle(angle.atom1.starting_index, angle.atom2.starting_index, angle.atom3.starting_index, angle.angle_type.theteq, 2*angle.angle_type.k*angle_frc_conv) for angle in self.angles_without_h: force.addAngle(angle.atom1.starting_index, angle.atom2.starting_index, angle.atom3.starting_index, angle.angle_type.theteq, 2*angle.angle_type.k*angle_frc_conv) system.addForce(force) # Add dihedral forces if verbose: print('Adding torsions...') force = mm.PeriodicTorsionForce() force.setForceGroup(self.DIHEDRAL_FORCE_GROUP) for tor in self.dihedrals_inc_h + self.dihedrals_without_h: force.addTorsion(tor.atom1.starting_index, tor.atom2.starting_index, tor.atom3.starting_index, tor.atom4.starting_index, int(tor.dihed_type.per), tor.dihed_type.phase, tor.dihed_type.phi_k*dihe_frc_conv) system.addForce(force) # Add nonbonded terms now if verbose: print('Adding nonbonded interactions...') force = mm.NonbondedForce() force.setForceGroup(self.NONBONDED_FORCE_GROUP) if self.ptr('ifbox') == 0: # non-periodic if nonbondedMethod is ff.NoCutoff: force.setNonbondedMethod(mm.NonbondedForce.NoCutoff) elif nonbondedMethod is ff.CutoffNonPeriodic: if cutoff is None: raise ValueError('No cutoff value specified') force.setNonbondedMethod(mm.NonbondedForce.CutoffNonPeriodic) force.setCutoffDistance(cutoff) else: raise ValueError('Illegal nonbonded method for non-periodic ' 'system') else: # periodic # Set up box vectors (from inpcrd if available, or fall back to # prmtop definitions system.setDefaultPeriodicBoxVectors(*self.box_vectors) # Set cutoff if cutoff is None: # Compute cutoff automatically box = self.box_lengths min_box_width = min((box[0]/u.nanometers, box[1]/u.nanometers, box[2]/u.nanometers)) CLEARANCE_FACTOR = 0.97 cutoff = u.Quantity((min_box_width*CLEARANCE_FACTOR)/2.0, u.nanometers) if nonbondedMethod is not ff.NoCutoff: force.setCutoffDistance(cutoff) # Set nonbonded method. if nonbondedMethod is ff.NoCutoff: force.setNonbondedMethod(mm.NonbondedForce.NoCutoff) elif nonbondedMethod is ff.CutoffNonPeriodic: force.setNonbondedMethod(mm.NonbondedForce.CutoffNonPeriodic) elif nonbondedMethod is ff.CutoffPeriodic: force.setNonbondedMethod(mm.NonbondedForce.CutoffPeriodic) elif nonbondedMethod is ff.Ewald: force.setNonbondedMethod(mm.NonbondedForce.Ewald) elif nonbondedMethod is ff.PME: force.setNonbondedMethod(mm.NonbondedForce.PME) else: raise ValueError('Cutoff method is not understood') if ewaldErrorTolerance is not None: force.setEwaldErrorTolerance(ewaldErrorTolerance) # Add per-particle nonbonded parameters (LJ params) sigma_scale = 2**(-1/6) * 2 for i, atm in enumerate(self.atom_list): force.addParticle(atm.charge, sigma_scale*LJ_radius[atm.nb_idx-1]*length_conv, LJ_depth[atm.nb_idx-1]*ene_conv) # Add 1-4 interactions excluded_atom_pairs = set() # save these pairs so we don't zero them out sigma_scale = 2**(-1/6) for tor in self.dihedrals_inc_h + self.dihedrals_without_h: if min(tor.signs) < 0: continue # multi-terms and impropers charge_prod = (tor.atom1.charge * tor.atom4.charge / tor.dihed_type.scee) epsilon = (sqrt(LJ_14_depth[tor.atom1.nb_idx-1] * ene_conv * LJ_14_depth[tor.atom4.nb_idx-1] * ene_conv) / tor.dihed_type.scnb) sigma = (LJ_14_radius[tor.atom1.nb_idx-1] + LJ_14_radius[tor.atom4.nb_idx-1])*length_conv*sigma_scale force.addException(tor.atom1.starting_index, tor.atom4.starting_index, charge_prod, sigma, epsilon) excluded_atom_pairs.add( min( (tor.atom1.starting_index, tor.atom4.starting_index), (tor.atom4.starting_index, tor.atom1.starting_index) ) ) # Add excluded atoms for atom in self.atom_list: # Exclude all bonds and angles for atom2 in atom.bond_partners: if atom2.starting_index > atom.starting_index: force.addException(atom.starting_index, atom2.starting_index, 0.0, 0.1, 0.0) for atom2 in atom.angle_partners: if atom2.starting_index > atom.starting_index: force.addException(atom.starting_index, atom2.starting_index, 0.0, 0.1, 0.0) for atom2 in atom.exclusion_partners: if atom2.starting_index > atom.starting_index: force.addException(atom.starting_index, atom2.starting_index, 0.0, 0.1, 0.0) for atom2 in atom.dihedral_partners: if atom2.starting_index <= atom.starting_index: continue if ((atom.starting_index, atom2.starting_index) in excluded_atom_pairs): continue force.addException(atom.starting_index, atom2.starting_index, 0.0, 0.1, 0.0) system.addForce(force) # Add virtual sites for water # First tag the residues that have an extra point in them for res in self.residue_list: res.has_ep = False ep = [atom for atom in self.atom_list if atom.atname in EPNAMES] for atom in ep: atom.residue.has_ep = True if len(ep) > 0: numRes = ep[-1].residue.idx + 1 waterO = [[] for i in range(numRes)] waterH = [[] for i in range(numRes)] waterEP = [[] for i in range(numRes)] for atom in self.atom_list: if atom.residue.has_ep: if atom.element == 8: waterO[res].append(atom) elif atom.element == 1: waterH[res].append(atom) elif atom.element == 0: waterEP[res].append(atom) # Record bond lengths for faster access distOH = [None] * numRes distHH = [None] * numRes distOE = [None] * numRes for bond in self.bonds_inc_h + self.bonds_without_h: a1 = bond.atom1 a2 = bond.atom2 if a1.residue.has_ep: res = a1.residue.idx if a1.element == 1 or a2.element == 1: if a1.element == 1 and a2.element == 1: distHH[res] = bond.bond_type.req * u.angstroms if a1.element == 8 or a2.element == 8: distOH[res] = bond.bond_type.req * u.angstroms elif ((a1.element == 8 or a2.element == 8) and (a1.element == 0 or a2.element == 0)): distOE[res] = bond.bond_type.req * u.angstroms # Loop over residues and add the virtual points out_of_plane_angle = 54.735 * u.degrees cosOOP = u.cos(out_of_plane_angle) sinOOP = u.sin(out_of_plane_angle) for residue in self.residue_list: if not residue.has_ep: continue res = residue.idx if len(waterO[res]) == 1 and len(waterH[res]) == 2: if len(waterEP[res]) == 1: # 4-point water weightH = distOE[res] / sqrt(distOH[res] * distOH[res] - 0.25 * distHH[res] * distHH[res]) system.setVirtualSite( waterEP[res][0], mm.ThreeParticleAverageSite(waterO[res][0], waterH[res][0], waterH[res][1], 1-weightH, weightH/2, weightH/2) ) elif len(waterEP[res]) == 2: # 5-point water weightH = (cosOOP * distOE[res] / sqrt(distOH[res] * distOH[res] - 0.25 * distHH[res] * distHH[res]) ) angleHOH = 2 * asin(0.5 * distHH[res] / distOH[res]) lenCross = distOH[res] * distOH[res] * sin(angleHOH) weightCross = sinOOP * distOE[res] / lenCross site1 = mm.OutOfPlaneSite(waterO[res][0], waterH[res][0], waterH[res][1], weightH/2, weightH/2, weightCross) site2 = mm.OutOfPlaneSite(waterO[res][0], waterH[res][0], waterH[res][1], weightH/2, weightH/2, -weightCross) system.setVirtualSite(waterEP[res][0], site1) system.setVirtualSite(waterEP[res][1], site2) # Add GB model if we're doing one if implicitSolvent is not None: if verbose: print('Adding GB parameters...') gb_parms = self._get_gb_params(implicitSolvent) # If implicitSolventKappa is None, compute it from salt # concentration if implicitSolventKappa is None: if u.is_quantity(implicitSolventSaltConc): sc = implicitSolventSaltConc.value_in_unit(u.moles/u.liter) implicitSolventSaltConc = sc if u.is_quantity(temperature): temperature = temperature.value_in_unit(u.kelvin) # The constant is 1 / sqrt( epsilon_0 * kB / (2 * NA * q^2 * # 1000) ) where NA is avogadro's number, epsilon_0 is the # permittivity of free space, q is the elementary charge (this # number matches sander/pmemd's kappa conversion factor) implicitSolventKappa = 50.33355 * sqrt(implicitSolventSaltConc / solventDielectric / temperature) # Multiply by 0.73 to account for ion exclusions, and multiply # by 10 to convert to 1/nm from 1/angstroms implicitSolventKappa *= 7.3 elif implicitSolvent is None: implicitSolventKappa = 0.0 if u.is_quantity(implicitSolventKappa): implicitSolventKappa = implicitSolventKappa.value_in_unit( (1.0/u.nanometer).unit) if implicitSolvent is HCT: gb = GBSAHCTForce(solventDielectric, soluteDielectric, None, cutoff, kappa=implicitSolventKappa) elif implicitSolvent is OBC1: gb = GBSAOBC1Force(solventDielectric, soluteDielectric, None, cutoff, kappa=implicitSolventKappa) elif implicitSolvent is OBC2: gb = GBSAOBC2Force(solventDielectric, soluteDielectric, None, cutoff, kappa=implicitSolventKappa) elif implicitSolvent is GBn: gb = GBSAGBnForce(solventDielectric, soluteDielectric, None, cutoff, kappa=implicitSolventKappa) elif implicitSolvent is GBn2: gb = GBSAGBn2Force(solventDielectric, soluteDielectric, None, cutoff, kappa=implicitSolventKappa) for i, atom in enumerate(self.atom_list): gb.addParticle([atom.charge] + list(gb_parms[i])) # Set cutoff method if nonbondedMethod is ff.NoCutoff: gb.setNonbondedMethod(mm.NonbondedForce.NoCutoff) elif nonbondedMethod is ff.CutoffNonPeriodic: gb.setNonbondedMethod(mm.NonbondedForce.CutoffNonPeriodic) gb.setCutoffDistance(cutoff) elif nonbondedMethod is ff.CutoffPeriodic: gb.setNonbondedMethod(mm.NonbondedForce.CutoffPeriodic) gb.setCutoffDistance(cutoff) else: raise ValueError('Illegal nonbonded method for use with GBSA') gb.setForceGroup(self.GB_FORCE_GROUP) system.addForce(gb) force.setReactionFieldDielectric(1.0) # applies to NonbondedForce # See if we repartition the hydrogen masses if hydrogenMass is not None: for bond in self.bonds_inc_h: atom1, atom2 = bond.atom1, bond.atom2 if atom1.element == 1: atom1, atom2 = atom2, atom1 # now atom2 is hydrogen for sure if atom1.element != 1: transfer_mass = hydrogenMass - atom2.mass new_mass1 = (system.getParticleMass(atom1.index) - transfer_mass) system.setParticleMass(atom2.index, hydrogenMass) system.setParticleMass(atom1.index, new_mass1) # See if we want to remove COM motion if removeCMMotion: system.addForce(mm.CMMotionRemover()) # Cache our system for easy access self._system = system return system
def readAmberSystem(prmtop_filename=None, prmtop_loader=None, shake=None, gbmodel=None, soluteDielectric=1.0, solventDielectric=78.5, nonbondedCutoff=None, nonbondedMethod='NoCutoff', scee=1.2, scnb=2.0, mm=None, verbose=False, EwaldErrorTolerance=None, flexibleConstraints=True, rigidWater=True): """ Create an OpenMM System from an Amber prmtop file. ARGUMENTS (specify one or the other, but not both) prmtop_filename (String) - name of Amber prmtop file (new-style only) prmtop_loader (PrmtopLoader) - the loaded prmtop file OPTIONAL ARGUMENTS shake (String) - if 'h-bonds', will SHAKE all bonds to hydrogen and water; if 'all-bonds', will SHAKE all bonds and water (default: None) gbmodel (String) - if 'OBC', OBC GBSA will be used; if 'GBVI', GB/VI will be used (default: None) soluteDielectric (float) - The solute dielectric constant to use in the implicit solvent model (default: 1.0) solventDielectric (float) - The solvent dielectric constant to use in the implicit solvent model (default: 78.5) nonbondedCutoff (float) - if specified, will set nonbondedCutoff (default: None) scnb (float) - 1-4 Lennard-Jones scaling factor (default: 1.2) scee (float) - 1-4 electrostatics scaling factor (default: 2.0) mm - if specified, this module will be used in place of pyopenmm (default: None) verbose (boolean) - if True, print out information on progress (default: False) flexibleConstraints (boolean) - if True, flexible bonds will be added in addition ot constrained bonds rigidWater (boolean=True) If true, water molecules will be fully rigid regardless of the value passed for the shake argument NOTES Even if bonds are SHAKEn, their harmonic stretch terms are still included in the potential. TODO Should these option names be changed to reflect their 'sander' counterparts? EXAMPLES Create a system of alanine dipeptide in implicit solvent. >>> directory = os.path.join(os.getenv('YANK_INSTALL_DIR'), 'test', 'systems', 'alanine-dipeptide-gbsa') >>> prmtop_filename = os.path.join(directory, 'alanine-dipeptide.prmtop') >>> system = readAmberSystem(prmtop_filename) Parse a prmtop file of alanine dipeptide in explicit solvent. >>> directory = os.path.join(os.getenv('YANK_INSTALL_DIR'), 'test', 'systems', 'alanine-dipeptide-explicit') >>> prmtop_filename = os.path.join(directory, 'alanine-dipeptide.prmtop') >>> system = readAmberSystem(prmtop_filename) """ if prmtop_filename is None and prmtop_loader is None: raise Exception("Must specify a filename or loader") if prmtop_filename is not None and prmtop_loader is not None: raise Exception("Cannot specify both a filename and a loader") if prmtop_filename is not None: # Load prmtop file. if verbose: print "Reading prmtop file '%s'..." % prmtop_filename prmtop = PrmtopLoader(prmtop_filename) else: prmtop = prmtop_loader if prmtop.getIfCap()>0: raise Exception("CAP option not currently supported") if prmtop.getIfPert()>0: raise Exception("perturbation not currently supported") if prmtop.getIfBox()>1: raise Exception("only standard periodic boxes are currently supported") # Use pyopenmm implementation of OpenMM by default. if mm is None: mm = simtk.openmm # Create OpenMM System. if verbose: print "Creating OpenMM system..." system = mm.System() # Populate system with atomic masses. if verbose: print "Adding particles..." for mass in prmtop.getMasses(): system.addParticle(mass) # Add constraints. isWater = [prmtop.getResidueLabel(i) == 'WAT' for i in range(prmtop.getNumAtoms())] if shake in ('h-bonds', 'all-bonds', 'h-angles'): for (iAtom, jAtom, k, rMin) in prmtop.getBondsWithH(): system.addConstraint(iAtom, jAtom, rMin) if shake in ('all-bonds', 'h-angles'): for (iAtom, jAtom, k, rMin) in prmtop.getBondsNoH(): system.addConstraint(iAtom, jAtom, rMin) if rigidWater and shake == None: for (iAtom, jAtom, k, rMin) in prmtop.getBondsWithH(): if isWater[iAtom] and isWater[jAtom]: system.addConstraint(iAtom, jAtom, rMin) # Add harmonic bonds. if verbose: print "Adding bonds..." force = mm.HarmonicBondForce() if flexibleConstraints or (shake not in ('h-bonds', 'all-bonds', 'h-angles')): for (iAtom, jAtom, k, rMin) in prmtop.getBondsWithH(): if flexibleConstraints or not (rigidWater and isWater[iAtom] and isWater[jAtom]): force.addBond(iAtom, jAtom, rMin, 2*k) if flexibleConstraints or (shake not in ('all-bonds', 'h-angles')): for (iAtom, jAtom, k, rMin) in prmtop.getBondsNoH(): force.addBond(iAtom, jAtom, rMin, 2*k) system.addForce(force) # Add harmonic angles. if verbose: print "Adding angles..." force = mm.HarmonicAngleForce() if shake == 'h-angles': numConstrainedBonds = system.getNumConstraints() atomConstraints = [[]]*system.getNumParticles() for i in range(system.getNumConstraints()): c = system.getConstraintParameters(i) distance = c[2].value_in_unit(units.nanometer) atomConstraints[c[0]].append((c[1], distance)) atomConstraints[c[1]].append((c[0], distance)) for (iAtom, jAtom, kAtom, k, aMin) in prmtop.getAngles(): if shake == 'h-angles': type1 = prmtop.getAtomType(iAtom) type2 = prmtop.getAtomType(jAtom) type3 = prmtop.getAtomType(kAtom) numH = len([type for type in (type1, type3) if type.startswith('H')]) constrained = (numH == 2 or (numH == 1 and type2.startswith('O'))) else: constrained = False if constrained: # Find the two bonds that make this angle. l1 = None l2 = None for bond in atomConstraints[jAtom]: if bond[0] == iAtom: l1 = bond[1] elif bond[0] == kAtom: l2 = bond[1] # Compute the distance between atoms and add a constraint length = math.sqrt(l1*l1 + l2*l2 - 2*l1*l2*math.cos(aMin)) system.addConstraint(iAtom, kAtom, length) if flexibleConstraints or not constrained: force.addAngle(iAtom, jAtom, kAtom, aMin, 2*k) system.addForce(force) # Add torsions. if verbose: print "Adding torsions..." force = mm.PeriodicTorsionForce() for (iAtom, jAtom, kAtom, lAtom, forceConstant, phase, periodicity) in prmtop.getDihedrals(): force.addTorsion(iAtom, jAtom, kAtom, lAtom, periodicity, phase, forceConstant) system.addForce(force) # Add nonbonded interactions. if verbose: print "Adding nonbonded interactions..." force = mm.NonbondedForce() if (prmtop.getIfBox() == 0): # System is non-periodic. if nonbondedMethod == 'NoCutoff': force.setNonbondedMethod(mm.NonbondedForce.NoCutoff) elif nonbondedMethod == 'CutoffNonPeriodic': if nonbondedCutoff is None: raise Exception("No cutoff value specified") force.setNonbondedMethod(mm.NonbondedForce.CutoffNonPeriodic) force.setCutoffDistance(nonbondedCutoff) else: raise Exception("Illegal nonbonded method for a non-periodic system") else: # System is periodic. # Set periodic box vectors for periodic system (boxBeta, boxX, boxY, boxZ) = prmtop.getBoxBetaAndDimensions() d0 = units.Quantity(0.0, units.angstroms) xVec = units.Quantity((boxX, d0, d0)) yVec = units.Quantity((d0, boxY, d0)) zVec = units.Quantity((d0, d0, boxZ)) system.setDefaultPeriodicBoxVectors(xVec, yVec, zVec) # Set cutoff. if nonbondedCutoff is None: # Compute cutoff automatically. min_box_width = min([boxX / units.nanometers, boxY / units.nanometers, boxZ / units.nanometers]) CLEARANCE_FACTOR = 0.97 # reduce the cutoff to be a bit smaller than 1/2 smallest box length nonbondedCutoff = units.Quantity((min_box_width * CLEARANCE_FACTOR) / 2.0, units.nanometers) if nonbondedMethod != 'NoCutoff': force.setCutoffDistance(nonbondedCutoff) # Set nonbonded method. if nonbondedMethod == 'NoCutoff': force.setNonbondedMethod(mm.NonbondedForce.NoCutoff) elif nonbondedMethod == 'CutoffNonPeriodic': force.setNonbondedMethod(mm.NonbondedForce.CutoffNonPeriodic) elif nonbondedMethod == 'CutoffPeriodic': force.setNonbondedMethod(mm.NonbondedForce.CutoffPeriodic) elif nonbondedMethod == 'Ewald': force.setNonbondedMethod(mm.NonbondedForce.Ewald) elif nonbondedMethod == 'PME': force.setNonbondedMethod(mm.NonbondedForce.PME) else: raise Exception("Cutoff method not understood.") if EwaldErrorTolerance is not None: force.setEwaldErrorTolerance(EwaldErrorTolerance) # Add per-particle nonbonded parameters. sigmaScale = 2**(-1./6.) * 2.0 for (charge, (rVdw, epsilon)) in zip(prmtop.getCharges(), prmtop.getNonbondTerms()): sigma = rVdw * sigmaScale force.addParticle(charge, sigma, epsilon) # Add 1-4 Interactions excludedAtomPairs = set() sigmaScale = 2**(-1./6.) for (iAtom, lAtom, chargeProd, rMin, epsilon) in prmtop.get14Interactions(): chargeProd /= scee epsilon /= scnb sigma = rMin * sigmaScale force.addException(iAtom, lAtom, chargeProd, sigma, epsilon) excludedAtomPairs.add(min((iAtom, lAtom), (lAtom, iAtom))) # Add Excluded Atoms excludedAtoms=prmtop.getExcludedAtoms() excludeParams = (0.0, 0.1, 0.0) for iAtom in range(prmtop.getNumAtoms()): for jAtom in excludedAtoms[iAtom]: if min((iAtom, jAtom), (jAtom, iAtom)) in excludedAtomPairs: continue force.addException(iAtom, jAtom, excludeParams[0], excludeParams[1], excludeParams[2]) system.addForce(force) # Add virtual sites for water. epNames = ['EP', 'LP'] ep = [i for i in range(prmtop.getNumAtoms()) if isWater[i] and prmtop.getAtomName(i)[:2] in epNames] if len(ep) > 0: epRes = set((prmtop.getResidueNumber(i) for i in ep)) numRes = max(epRes)+1 # For each residue that contains an "extra point", find the oxygen, hydrogens, and points. waterO = [] waterH = [] waterEP = [] for i in range(numRes): waterO.append([]) waterH.append([]) waterEP.append([]) for i in range(prmtop.getNumAtoms()): res = prmtop.getResidueNumber(i) if res in epRes: name = prmtop.getAtomName(i) if name[0] == 'O': waterO[res].append(i) if name[0] == 'H': waterH[res].append(i) if name[:2] in epNames: waterEP[res].append(i) # Record bond lengths for faster access. distOH = [None]*numRes distHH = [None]*numRes distOE = [None]*numRes for (atom1, atom2, k, dist) in prmtop.getBondsWithH()+prmtop.getBondsNoH(): res = prmtop.getResidueNumber(atom1) if res in epRes: name1 = prmtop.getAtomName(atom1) name2 = prmtop.getAtomName(atom2) if name1[0] == 'H' or name2[0] == 'H': if name1[0] == 'H' and name2[0] == 'H': distHH[res] = dist if name1[0] == 'O' or name2[0] == 'O': distOH[res] = dist elif (name1[0] == 'O' or name2[0] == 'O') and ((name1[:2] in epNames or name2[:2] in epNames)): distOE[res] = dist # Loop over residues and add the virtual sites. outOfPlaneAngle = 54.735*units.degree cosOOP = units.cos(outOfPlaneAngle) sinOOP = units.sin(outOfPlaneAngle) for res in range(numRes): if len(waterO[res]) == 1 and len(waterH[res]) == 2: if len(waterEP[res]) == 1: # Four point water weightH = distOE[res]/math.sqrt(distOH[res]**2-(0.5*distHH[res])**2) system.setVirtualSite(waterEP[res][0], mm.ThreeParticleAverageSite(waterO[res][0], waterH[res][0], waterH[res][1], 1-weightH, weightH/2, weightH/2)) elif len(waterEP[res]) == 2: # Five point water weightH = cosOOP*distOE[res]/math.sqrt(distOH[res]**2-(0.5*distHH[res])**2) angleHOH = 2*math.asin(0.5*distHH[res]/distOH[res]) lenCross = (distOH[res]**2)*math.sin(angleHOH) weightCross = sinOOP*distOE[res]/lenCross system.setVirtualSite(waterEP[res][0], mm.OutOfPlaneSite(waterO[res][0], waterH[res][0], waterH[res][1], weightH/2, weightH/2, weightCross)) system.setVirtualSite(waterEP[res][1], mm.OutOfPlaneSite(waterO[res][0], waterH[res][0], waterH[res][1], weightH/2, weightH/2, -weightCross)) # Add GBSA model. if gbmodel is not None: if verbose: print "Adding GB parameters..." charges = prmtop.getCharges() symbls = None cutoff = None if nonbondedMethod != 'NoCutoff': cutoff = nonbondedCutoff if units.is_quantity(cutoff): cutoff = cutoff.value_in_unit(units.nanometers) if gbmodel == 'GBn': symbls = prmtop.getAtomTypes() gb_parms = prmtop.getGBParms(symbls) if gbmodel == 'HCT': gb = customgb.GBSAHCTForce(solventDielectric, soluteDielectric, 'ACE', cutoff) elif gbmodel == 'OBC1': gb = customgb.GBSAOBC1Force(solventDielectric, soluteDielectric, 'ACE', cutoff) elif gbmodel == 'OBC2': gb = mm.GBSAOBCForce() gb.setSoluteDielectric(soluteDielectric) gb.setSolventDielectric(solventDielectric) elif gbmodel == 'GBn': gb = customgb.GBSAGBnForce(solventDielectric, soluteDielectric, 'ACE', cutoff) else: raise Exception("Illegal value specified for implicit solvent model") for iAtom in range(prmtop.getNumAtoms()): if gbmodel == 'OBC2': gb.addParticle(charges[iAtom], gb_parms[iAtom][0], gb_parms[iAtom][1]) else: gb.addParticle([charges[iAtom], gb_parms[iAtom][0], gb_parms[iAtom][1]]) system.addForce(gb) if nonbondedMethod == 'NoCutoff': gb.setNonbondedMethod(mm.NonbondedForce.NoCutoff) elif nonbondedMethod == 'CutoffNonPeriodic': gb.setNonbondedMethod(mm.NonbondedForce.CutoffNonPeriodic) gb.setCutoffDistance(nonbondedCutoff) elif nonbondedMethod == 'CutoffPeriodic': gb.setNonbondedMethod(mm.NonbondedForce.CutoffPeriodic) gb.setCutoffDistance(nonbondedCutoff) else: raise Exception("Illegal nonbonded method for use with GBSA") force.setReactionFieldDielectric(1.0) # TODO: Add GBVI terms? return system
def testUnitMathModuleBadInput(self): """ Tests that bad units to unit_math fails appropriately """ self.assertRaises(ArithmeticError, lambda: u.sqrt(9*u.meters)) self.assertRaises(TypeError, lambda: u.sin(1*u.meters))
def readAmberSystem(prmtop_filename=None, prmtop_loader=None, shake=None, gbmodel=None, soluteDielectric=1.0, solventDielectric=78.5, nonbondedCutoff=None, nonbondedMethod='NoCutoff', scee=1.2, scnb=2.0, mm=None, verbose=False, EwaldErrorTolerance=None, flexibleConstraints=True, rigidWater=True): """ Create an OpenMM System from an Amber prmtop file. ARGUMENTS (specify one or the other, but not both) prmtop_filename (String) - name of Amber prmtop file (new-style only) prmtop_loader (PrmtopLoader) - the loaded prmtop file OPTIONAL ARGUMENTS shake (String) - if 'h-bonds', will SHAKE all bonds to hydrogen and water; if 'all-bonds', will SHAKE all bonds and water (default: None) gbmodel (String) - if 'OBC', OBC GBSA will be used; if 'GBVI', GB/VI will be used (default: None) soluteDielectric (float) - The solute dielectric constant to use in the implicit solvent model (default: 1.0) solventDielectric (float) - The solvent dielectric constant to use in the implicit solvent model (default: 78.5) nonbondedCutoff (float) - if specified, will set nonbondedCutoff (default: None) scnb (float) - 1-4 Lennard-Jones scaling factor (default: 1.2) scee (float) - 1-4 electrostatics scaling factor (default: 2.0) mm - if specified, this module will be used in place of pyopenmm (default: None) verbose (boolean) - if True, print out information on progress (default: False) flexibleConstraints (boolean) - if True, flexible bonds will be added in addition ot constrained bonds rigidWater (boolean=True) If true, water molecules will be fully rigid regardless of the value passed for the shake argument NOTES Even if bonds are SHAKEn, their harmonic stretch terms are still included in the potential. TODO Should these option names be changed to reflect their 'sander' counterparts? EXAMPLES Create a system of alanine dipeptide in implicit solvent. >>> directory = os.path.join(os.getenv('YANK_INSTALL_DIR'), 'test', 'systems', 'alanine-dipeptide-gbsa') >>> prmtop_filename = os.path.join(directory, 'alanine-dipeptide.prmtop') >>> system = readAmberSystem(prmtop_filename) Parse a prmtop file of alanine dipeptide in explicit solvent. >>> directory = os.path.join(os.getenv('YANK_INSTALL_DIR'), 'test', 'systems', 'alanine-dipeptide-explicit') >>> prmtop_filename = os.path.join(directory, 'alanine-dipeptide.prmtop') >>> system = readAmberSystem(prmtop_filename) """ if prmtop_filename is None and prmtop_loader is None: raise Exception("Must specify a filename or loader") if prmtop_filename is not None and prmtop_loader is not None: raise Exception("Cannot specify both a filename and a loader") if prmtop_filename is not None: # Load prmtop file. if verbose: print "Reading prmtop file '%s'..." % prmtop_filename prmtop = PrmtopLoader(prmtop_filename) else: prmtop = prmtop_loader if prmtop.getIfCap() > 0: raise Exception("CAP option not currently supported") if prmtop.getIfPert() > 0: raise Exception("perturbation not currently supported") if prmtop.getIfBox() > 1: raise Exception("only standard periodic boxes are currently supported") # Use pyopenmm implementation of OpenMM by default. if mm is None: mm = simtk.openmm # Create OpenMM System. if verbose: print "Creating OpenMM system..." system = mm.System() # Populate system with atomic masses. if verbose: print "Adding particles..." for mass in prmtop.getMasses(): system.addParticle(mass) # Add constraints. isWater = [ prmtop.getResidueLabel(i) == 'WAT' for i in range(prmtop.getNumAtoms()) ] if shake in ('h-bonds', 'all-bonds', 'h-angles'): for (iAtom, jAtom, k, rMin) in prmtop.getBondsWithH(): system.addConstraint(iAtom, jAtom, rMin) if shake in ('all-bonds', 'h-angles'): for (iAtom, jAtom, k, rMin) in prmtop.getBondsNoH(): system.addConstraint(iAtom, jAtom, rMin) if rigidWater and shake == None: for (iAtom, jAtom, k, rMin) in prmtop.getBondsWithH(): if isWater[iAtom] and isWater[jAtom]: system.addConstraint(iAtom, jAtom, rMin) # Add harmonic bonds. if verbose: print "Adding bonds..." force = mm.HarmonicBondForce() if flexibleConstraints or (shake not in ('h-bonds', 'all-bonds', 'h-angles')): for (iAtom, jAtom, k, rMin) in prmtop.getBondsWithH(): if flexibleConstraints or not (rigidWater and isWater[iAtom] and isWater[jAtom]): force.addBond(iAtom, jAtom, rMin, 2 * k) if flexibleConstraints or (shake not in ('all-bonds', 'h-angles')): for (iAtom, jAtom, k, rMin) in prmtop.getBondsNoH(): force.addBond(iAtom, jAtom, rMin, 2 * k) system.addForce(force) # Add harmonic angles. if verbose: print "Adding angles..." force = mm.HarmonicAngleForce() if shake == 'h-angles': numConstrainedBonds = system.getNumConstraints() atomConstraints = [[]] * system.getNumParticles() for i in range(system.getNumConstraints()): c = system.getConstraintParameters(i) distance = c[2].value_in_unit(units.nanometer) atomConstraints[c[0]].append((c[1], distance)) atomConstraints[c[1]].append((c[0], distance)) for (iAtom, jAtom, kAtom, k, aMin) in prmtop.getAngles(): if shake == 'h-angles': type1 = prmtop.getAtomType(iAtom) type2 = prmtop.getAtomType(jAtom) type3 = prmtop.getAtomType(kAtom) numH = len( [type for type in (type1, type3) if type.startswith('H')]) constrained = (numH == 2 or (numH == 1 and type2.startswith('O'))) else: constrained = False if constrained: # Find the two bonds that make this angle. l1 = None l2 = None for bond in atomConstraints[jAtom]: if bond[0] == iAtom: l1 = bond[1] elif bond[0] == kAtom: l2 = bond[1] # Compute the distance between atoms and add a constraint length = math.sqrt(l1 * l1 + l2 * l2 - 2 * l1 * l2 * math.cos(aMin)) system.addConstraint(iAtom, kAtom, length) if flexibleConstraints or not constrained: force.addAngle(iAtom, jAtom, kAtom, aMin, 2 * k) system.addForce(force) # Add torsions. if verbose: print "Adding torsions..." force = mm.PeriodicTorsionForce() for (iAtom, jAtom, kAtom, lAtom, forceConstant, phase, periodicity) in prmtop.getDihedrals(): force.addTorsion(iAtom, jAtom, kAtom, lAtom, periodicity, phase, forceConstant) system.addForce(force) # Add nonbonded interactions. if verbose: print "Adding nonbonded interactions..." force = mm.NonbondedForce() if (prmtop.getIfBox() == 0): # System is non-periodic. if nonbondedMethod == 'NoCutoff': force.setNonbondedMethod(mm.NonbondedForce.NoCutoff) elif nonbondedMethod == 'CutoffNonPeriodic': if nonbondedCutoff is None: raise Exception("No cutoff value specified") force.setNonbondedMethod(mm.NonbondedForce.CutoffNonPeriodic) force.setCutoffDistance(nonbondedCutoff) else: raise Exception( "Illegal nonbonded method for a non-periodic system") else: # System is periodic. # Set periodic box vectors for periodic system (boxBeta, boxX, boxY, boxZ) = prmtop.getBoxBetaAndDimensions() d0 = units.Quantity(0.0, units.angstroms) xVec = units.Quantity((boxX, d0, d0)) yVec = units.Quantity((d0, boxY, d0)) zVec = units.Quantity((d0, d0, boxZ)) system.setDefaultPeriodicBoxVectors(xVec, yVec, zVec) # Set cutoff. if nonbondedCutoff is None: # Compute cutoff automatically. min_box_width = min([ boxX / units.nanometers, boxY / units.nanometers, boxZ / units.nanometers ]) CLEARANCE_FACTOR = 0.97 # reduce the cutoff to be a bit smaller than 1/2 smallest box length nonbondedCutoff = units.Quantity( (min_box_width * CLEARANCE_FACTOR) / 2.0, units.nanometers) if nonbondedMethod != 'NoCutoff': force.setCutoffDistance(nonbondedCutoff) # Set nonbonded method. if nonbondedMethod == 'NoCutoff': force.setNonbondedMethod(mm.NonbondedForce.NoCutoff) elif nonbondedMethod == 'CutoffNonPeriodic': force.setNonbondedMethod(mm.NonbondedForce.CutoffNonPeriodic) elif nonbondedMethod == 'CutoffPeriodic': force.setNonbondedMethod(mm.NonbondedForce.CutoffPeriodic) elif nonbondedMethod == 'Ewald': force.setNonbondedMethod(mm.NonbondedForce.Ewald) elif nonbondedMethod == 'PME': force.setNonbondedMethod(mm.NonbondedForce.PME) else: raise Exception("Cutoff method not understood.") if EwaldErrorTolerance is not None: force.setEwaldErrorTolerance(EwaldErrorTolerance) # Add per-particle nonbonded parameters. sigmaScale = 2**(-1. / 6.) * 2.0 for (charge, (rVdw, epsilon)) in zip(prmtop.getCharges(), prmtop.getNonbondTerms()): sigma = rVdw * sigmaScale force.addParticle(charge, sigma, epsilon) # Add 1-4 Interactions excludedAtomPairs = set() sigmaScale = 2**(-1. / 6.) for (iAtom, lAtom, chargeProd, rMin, epsilon) in prmtop.get14Interactions(): chargeProd /= scee epsilon /= scnb sigma = rMin * sigmaScale force.addException(iAtom, lAtom, chargeProd, sigma, epsilon) excludedAtomPairs.add(min((iAtom, lAtom), (lAtom, iAtom))) # Add Excluded Atoms excludedAtoms = prmtop.getExcludedAtoms() excludeParams = (0.0, 0.1, 0.0) for iAtom in range(prmtop.getNumAtoms()): for jAtom in excludedAtoms[iAtom]: if min((iAtom, jAtom), (jAtom, iAtom)) in excludedAtomPairs: continue force.addException(iAtom, jAtom, excludeParams[0], excludeParams[1], excludeParams[2]) system.addForce(force) # Add virtual sites for water. epNames = ['EP', 'LP'] ep = [ i for i in range(prmtop.getNumAtoms()) if isWater[i] and prmtop.getAtomName(i)[:2] in epNames ] if len(ep) > 0: epRes = set((prmtop.getResidueNumber(i) for i in ep)) numRes = max(epRes) + 1 # For each residue that contains an "extra point", find the oxygen, hydrogens, and points. waterO = [] waterH = [] waterEP = [] for i in range(numRes): waterO.append([]) waterH.append([]) waterEP.append([]) for i in range(prmtop.getNumAtoms()): res = prmtop.getResidueNumber(i) if res in epRes: name = prmtop.getAtomName(i) if name[0] == 'O': waterO[res].append(i) if name[0] == 'H': waterH[res].append(i) if name[:2] in epNames: waterEP[res].append(i) # Record bond lengths for faster access. distOH = [None] * numRes distHH = [None] * numRes distOE = [None] * numRes for (atom1, atom2, k, dist) in prmtop.getBondsWithH() + prmtop.getBondsNoH(): res = prmtop.getResidueNumber(atom1) if res in epRes: name1 = prmtop.getAtomName(atom1) name2 = prmtop.getAtomName(atom2) if name1[0] == 'H' or name2[0] == 'H': if name1[0] == 'H' and name2[0] == 'H': distHH[res] = dist if name1[0] == 'O' or name2[0] == 'O': distOH[res] = dist elif (name1[0] == 'O' or name2[0] == 'O') and ( (name1[:2] in epNames or name2[:2] in epNames)): distOE[res] = dist # Loop over residues and add the virtual sites. outOfPlaneAngle = 54.735 * units.degree cosOOP = units.cos(outOfPlaneAngle) sinOOP = units.sin(outOfPlaneAngle) for res in range(numRes): if len(waterO[res]) == 1 and len(waterH[res]) == 2: if len(waterEP[res]) == 1: # Four point water weightH = distOE[res] / math.sqrt(distOH[res]**2 - (0.5 * distHH[res])**2) system.setVirtualSite( waterEP[res][0], mm.ThreeParticleAverageSite(waterO[res][0], waterH[res][0], waterH[res][1], 1 - weightH, weightH / 2, weightH / 2)) elif len(waterEP[res]) == 2: # Five point water weightH = cosOOP * distOE[res] / math.sqrt( distOH[res]**2 - (0.5 * distHH[res])**2) angleHOH = 2 * math.asin(0.5 * distHH[res] / distOH[res]) lenCross = (distOH[res]**2) * math.sin(angleHOH) weightCross = sinOOP * distOE[res] / lenCross system.setVirtualSite( waterEP[res][0], mm.OutOfPlaneSite(waterO[res][0], waterH[res][0], waterH[res][1], weightH / 2, weightH / 2, weightCross)) system.setVirtualSite( waterEP[res][1], mm.OutOfPlaneSite(waterO[res][0], waterH[res][0], waterH[res][1], weightH / 2, weightH / 2, -weightCross)) # Add GBSA model. if gbmodel is not None: if verbose: print "Adding GB parameters..." charges = prmtop.getCharges() symbls = None if gbmodel == 'GBn': symbls = prmtop.getAtomTypes() gb_parms = prmtop.getGBParms(symbls) if gbmodel == 'HCT': gb = customgb.GBSAHCTForce(solventDielectric, soluteDielectric, 'ACE') elif gbmodel == 'OBC1': gb = customgb.GBSAOBC1Force(solventDielectric, soluteDielectric, 'ACE') elif gbmodel == 'OBC2': gb = mm.GBSAOBCForce() gb.setSoluteDielectric(soluteDielectric) gb.setSolventDielectric(solventDielectric) elif gbmodel == 'GBn': gb = customgb.GBSAGBnForce(solventDielectric, soluteDielectric, 'ACE') else: raise Exception( "Illegal value specified for implicit solvent model") for iAtom in range(prmtop.getNumAtoms()): if gbmodel == 'OBC2': gb.addParticle(charges[iAtom], gb_parms[iAtom][0], gb_parms[iAtom][1]) else: gb.addParticle( [charges[iAtom], gb_parms[iAtom][0], gb_parms[iAtom][1]]) system.addForce(gb) if nonbondedMethod == 'NoCutoff': gb.setNonbondedMethod(mm.NonbondedForce.NoCutoff) elif nonbondedMethod == 'CutoffNonPeriodic': gb.setNonbondedMethod(mm.NonbondedForce.CutoffNonPeriodic) gb.setCutoffDistance(nonbondedCutoff) elif nonbondedMethod == 'CutoffPeriodic': gb.setNonbondedMethod(mm.NonbondedForce.CutoffPeriodic) gb.setCutoffDistance(nonbondedCutoff) else: raise Exception("Illegal nonbonded method for use with GBSA") force.setReactionFieldDielectric(1.0) # TODO: Add GBVI terms? return system
def get_standard_state_correction_static(method='numerical', **kwargs): """ Compute the standard state correction for the arbitrary restraint energy function. Returns ------- DeltaG : float Computed standard-state correction in dimensionless units (kT) Notes ----- 'analytical' uses analytical approach from [1], but this approach is known to be inexact. 'numerical' uses numerical integral to the partition function contriubtions for r and theta, analytical for phi """ class Bunch(object): """Make a dict accessible via an object accessor""" def __init__(self, adict): self.__dict__.update(adict) def strip(passed_unit): """Cast the passed_unit into md unit system for integrand lambda functions""" return passed_unit.value_in_unit_system(unit.md_unit_system) all_valid_keys = ['kT', 'parameters'] for key in all_valid_keys: if key not in kwargs: raise KeyError('Missing {} from input arguments!'.format(key)) kT = kwargs['kT'] pi = np.pi p = Bunch(kwargs['parameters']) DeltaG = 0 # Multiply by unit.radian**5 to remove the expected unit value # radians is a soft unit in this case, it cancels in the math, but not in the equations here. if method is 'analytical': # Eq 32 of Ref [1] DeltaG += -np.log( \ (8. * pi ** 2 * V0) / (p.r_aA0 ** 2 * unit.sin(p.theta_A0) * unit.sin(p.theta_B0)) \ * unit.sqrt(p.K_r * p.K_thetaA * p.K_thetaB * p.K_phiA * p.K_phiB * p.K_phiC) / (2 * pi * kT) ** 3 \ * unit.radian**5) elif method is 'numerical': # Radial sigma = 1 / unit.sqrt(p.K_r / kT) rmin = min(0*unit.angstrom, p.r_aA0 - 8 * sigma) rmax = p.r_aA0 + 8 * sigma I = lambda r: r ** 2 * np.exp(-strip(p.K_r) / (2 * strip(kT)) * (r - strip(p.r_aA0)) ** 2) DGIntegral, dDGIntegral = scipy.integrate.quad(I, strip(rmin), strip(rmax)) * unit.nanometer**3 ExpDeltaG = DGIntegral # Angular for name in ['A', 'B']: theta0 = getattr(p, 'theta_' + name + '0') K_theta = getattr(p, 'K_theta' + name) I = lambda theta: np.sin(theta) * np.exp(-strip(K_theta) / (2 * strip(kT)) * (theta - strip(theta0)) ** 2) DGIntegral, dDGIntegral = scipy.integrate.quad(I, 0, pi) ExpDeltaG *= DGIntegral # Torsion for name in ['A', 'B', 'C']: phi0 = getattr(p, 'phi_' + name + '0') K_phi = getattr(p, 'K_phi' + name) kshort = strip(K_phi/kT) ExpDeltaG *= math.sqrt(pi/2.0) * ( math.erf((strip(phi0)+pi)*unit.sqrt(kshort)/math.sqrt(2)) - math.erf((strip(phi0)-pi)*unit.sqrt(kshort)/math.sqrt(2)) ) / unit.sqrt(kshort) DeltaG += -np.log(8 * pi**2 * V0 / ExpDeltaG) else: raise ValueError('"method" must be "analytical" or "numerical"') return DeltaG