def initialize(self, integrator): sigma_v = unit.sqrt(self._kT_value / self._m_value) integrator.setGlobalVariableByName( self._v, sigma_v * integrator._random.normal()) if self._thermostat == 'Nose-Hoover': sigma_v_eta = unit.sqrt(self._kT_value / self._Q_eta_value) integrator.setGlobalVariableByName( self._v_eta, sigma_v_eta * integrator._random.normal())
def compute_Ka(DG, dDG): """Compute the association constant from the free energy. Parameters ---------- DG : simtk.Quantity Free energy dDG : simtk.Quantity Uncertainty in free energy Returns ------- Ka : simtk.Quantity Association constant. dKa : simtk.Quantity Association constant uncertainty. """ concentration_unit = u.molar Ka = np.exp(-DG / (R * T)) * 1 / concentration_unit # Propagate error. if dDG is None: dKa = None else: dKadDG = -Ka / (R * T) # Derivative dKa(DG)/dDG. dKa = u.sqrt(dKadDG**2 * dDG**2) return Ka, dKa
def compute_TDS(DG, dDG, DH, dDH): """Compute the entropy from free energy and enthalpy. Parameters ---------- DG : simtk.Quantity Free energy. dDG : simtk.Quantity Free energy uncertainty. DH : simtk.Quantity Enthalpy. dDH : simtk.Quantity Enthalpy uncertainty. Returns ------- TDS : simtk.Quantity Entrop. dTDS : simtk.Quantity Binding free energy uncertainty. """ TDS = DH - DG dTDS = u.sqrt(dDH**2 + dDG**2) return TDS, dTDS
def generate_maxwell_boltzmann_velocities(self, temperature): """ Generate random velocities for the solute. """ assert self.masses_list is not None assert self.n_atoms is not None # Initiate array vel = unit.Quantity( np.zeros([self.n_atoms, 3], np.float64), unit.nanometer / unit.picosecond ) # velocities[i,k] is the kth component of the velocity of atom i kT = temperature * unit.BOLTZMANN_CONSTANT_kB kT = kT.in_units_of(unit.kilogram * unit.meter * unit.meter / (unit.second * unit.second)) # Assign velocities from the Maxwell-Boltzmann distribution. for atom_index in range(self.n_atoms): mass = self.masses_list[atom_index] if mass._value > 1e-8: mass = unit.Quantity(mass._value * 1.66054e-27, unit.kilogram) # Standard deviation of velocity distribution for each coordinate for this atom sigma = unit.sqrt(kT / mass) else: sigma = 0.0 * unit.nanometer / unit.picosecond for k in range(3): # 0.001 is to take into account the ns / ps vel[atom_index, k] = (sigma * np.random.standard_normal()) return vel.in_units_of(unit.nanometer / unit.picosecond)
def test_propertyestimator(): """Test PhysicalPropertyEstimator on synthetic data. """ # Get synthetic dataset and parameters from openforcefield import testsystems dataset = testsystems.TestDataset() parameters = testsystems.TestParameterSet() # Create a PropertyEstimator from openforcefield import PropertyEstimator estimator = PropertyEstimator() # Compute properties computed_properties = estimator.computeProperties(dataset, parameters) # Assert that computed properties are within statistical error for (measured_property, computed_property) in zip(dataset, computed_properties): error_value = (computed_property.value - measured_property.value) error_uncertainty = unit.sqrt(computed_property.uncertainty**2 + measured_property.uncertainty**2) relative_error = error_value / error_uncertainty if (relative_error > SIGMA_CUTOFF): msg = 'Computed property %s differs from measured property by more than SIGMA_CUTOFF (%f):\n' % (computed_property.name, SIGMA_CUTOFF) msg += 'Measured: %12.3f +- %12.3f %s' % (measured_property.value / measured_property.unit, measured_property.uncertainty / measured_property.unit, str(measured_property.unit)) msg += 'Computed: %12.3f +- %12.3f %s' % (computed_property.value / measured_property.unit, computed_property.uncertainty / measured_property.unit, str(measured_property.unit)) msg += 'ERROR : %12.3f +- %12.3f %s (%12.3f SIGMA)' % (error_value / measured_property.unit, error_uncertainty / measured_property.unit, str(measured_property.unit), relative_error) raise Exception(msg)
def createDisulfideBonds(self, positions): """Identify disulfide bonds based on proximity and add them to the Topology. Parameters ---------- positions : list The list of atomic positions based on which to identify bonded atoms """ def isCyx(res): names = [atom.name for atom in res._atoms] return 'SG' in names and 'HG' not in names cyx = [res for res in self.residues() if res.name == 'CYS' and isCyx(res)] atomNames = [[atom.name for atom in res._atoms] for res in cyx] for i in range(len(cyx)): sg1 = cyx[i]._atoms[atomNames[i].index('SG')] pos1 = positions[sg1.index] for j in range(i): sg2 = cyx[j]._atoms[atomNames[j].index('SG')] pos2 = positions[sg2.index] delta = [x-y for (x,y) in zip(pos1, pos2)] distance = sqrt(delta[0]*delta[0] + delta[1]*delta[1] + delta[2]*delta[2]) if distance < 0.3*nanometers: self.addBond(sg1, sg2)
def compute_DG(Ka, dKa): """Compute the free energy from the association constant. Parameters ---------- Ka : simtk.Quantity Association constant. dKa : simtk.Quantity Association constant uncertainty. Returns ------- DG : simtk.Quantity Binding free energy. dDG : simtk.Quantity Binding free energy uncertainty. """ concentration_unit = 1 / Ka.unit DG = -R * T * np.log(Ka * concentration_unit) # Propagate error. if dKa is None: dDG = None else: dDGdKa = -R * T / Ka # Derivative dDG(Ka)/dKa. # Have to use u.sqrt to avoid bug with simtk.unit dDG = u.sqrt(dDGdKa**2 * dKa**2) return DG, dDG
def createDisulfideBonds(self, positions): """Identify disulfide bonds based on proximity and add them to the Topology. Parameters: - positions (list) The list of atomic positions based on which to identify bonded atoms """ def isCyx(res): names = [atom.name for atom in res._atoms] return 'SG' in names and 'HG' not in names cyx = [ res for res in self.residues() if res.name == 'CYS' and isCyx(res) ] atomNames = [[atom.name for atom in res._atoms] for res in cyx] for i in range(len(cyx)): sg1 = cyx[i]._atoms[atomNames[i].index('SG')] pos1 = positions[sg1.index] for j in range(i): sg2 = cyx[j]._atoms[atomNames[j].index('SG')] pos2 = positions[sg2.index] delta = [x - y for (x, y) in zip(pos1, pos2)] distance = sqrt(delta[0] * delta[0] + delta[1] * delta[1] + delta[2] * delta[2]) if distance < 0.3 * nanometers: self.addBond(sg1, sg2)
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 test_propertyestimator(): """Test PhysicalPropertyEstimator on synthetic data. """ # Get synthetic dataset and parameters from openforcefield.tests import testsystems dataset = testsystems.TestDataset() parameters = testsystems.TestParameterSet() # Create a PropertyEstimator from openforcefield.propertyestimator import PropertyEstimator estimator = PropertyEstimator() # Compute properties computed_properties = estimator.computeProperties(dataset, parameters) # Assert that computed properties are within statistical error for (measured_property, computed_property) in zip(dataset, computed_properties): error_value = (computed_property.value - measured_property.value) error_uncertainty = unit.sqrt(computed_property.uncertainty**2 + measured_property.uncertainty**2) relative_error = error_value / error_uncertainty if (relative_error > SIGMA_CUTOFF): msg = 'Computed property %s differs from measured property by more than SIGMA_CUTOFF (%f):\n' % (computed_property.name, SIGMA_CUTOFF) msg += 'Measured: %12.3f +- %12.3f %s' % (measured_property.value / measured_property.unit, measured_property.uncertainty / measured_property.unit, str(measured_property.unit)) msg += 'Computed: %12.3f +- %12.3f %s' % (computed_property.value / measured_property.unit, computed_property.uncertainty / measured_property.unit, str(measured_property.unit)) msg += 'ERROR : %12.3f +- %12.3f %s (%12.3f SIGMA)' % (error_value / measured_property.unit, error_uncertainty / measured_property.unit, str(measured_property.unit), relative_error) raise Exception(msg)
def add_rosetta_exception_parameters(cgmodel, nonbonded_force, particle_index_1, particle_index_2): """ """ exception_list = [] for exception in range(nonbonded_force.getNumExceptions()): index_1, index_2, charge, sigma, epsilon = nonbonded_force.getExceptionParameters( exception) if [index_1, index_2] not in exception_list and [ index_2, index_1 ] not in exception_list: exception_list.append([index_1, index_2]) if [particle_index_1, particle_index_2] not in exception_list and [ particle_index_2, particle_index_1, ] not in exception_list: charge_1 = cgmodel.get_particle_charge(particle_index_1) sigma_1 = cgmodel.get_particle_sigma(particle_index_1).in_units_of( unit.nanometer) epsilon_1 = cgmodel.get_particle_epsilon(particle_index_1).in_units_of( unit.kilojoule_per_mole) charge_2 = cgmodel.get_particle_charge(particle_index_2) sigma_2 = cgmodel.get_particle_sigma(particle_index_2).in_units_of( unit.nanometer) epsilon_2 = cgmodel.get_particle_epsilon(particle_index_2).in_units_of( unit.kilojoule_per_mole) sigma = (sigma_1 + sigma_2) / 2.0 epsilon = 0.2 * unit.sqrt(epsilon_1 * epsilon_2) nonbonded_force.addException(particle_index_1, particle_index_2, 0.2 * charge_1 * charge_2, sigma, epsilon) return nonbonded_force
def __init__(self, **kwargs): super(HarmonicOscillatorSimulatedTempering, self).__init__(**kwargs) self.description = 'Harmonic oscillator simulated tempering simulation' # Create topology, positions, and system. from openmmtools.testsystems import HarmonicOscillator K = 1.0 * unit.kilocalories_per_mole / unit.angstroms**2 # 3D harmonic oscillator spring constant mass = 39.948 * unit.amu # 3D harmonic oscillator particle mass period = 2.0 * np.pi * unit.sqrt( mass / K) # harmonic oscillator period timestep = 0.01 * period testsystem = HarmonicOscillator(K=K, mass=mass) self.topology = testsystem.topology self.positions = testsystem.positions self.system = testsystem.system # Create thermodynamic states. Tmin = 100 * unit.kelvin Tmax = 1000 * unit.kelvin ntemps = 8 # number of temperatures from sams import ThermodynamicState temperatures = unit.Quantity( np.logspace(np.log10(Tmin / unit.kelvin), np.log10(Tmax / unit.kelvin), ntemps), unit.kelvin) self.thermodynamic_states = [ ThermodynamicState(system=self.system, temperature=temperature) for temperature in temperatures ] # Compute analytical logZ for each thermodynamic state. self.logZ = np.zeros([ntemps], np.float64) for (index, thermodynamic_state) in enumerate(self.thermodynamic_states): beta = thermodynamic_state.beta self.logZ[index] = -1.5 * np.log(beta * K * unit.angstrom**2) self.logZ[:] -= self.logZ[0] # Create SAMS samplers from sams.samplers import SamplerState, MCMCSampler, ExpandedEnsembleSampler, SAMSSampler thermodynamic_state_index = 0 # initial thermodynamic state index thermodynamic_state = self.thermodynamic_states[ thermodynamic_state_index] sampler_state = SamplerState(positions=self.positions) self.mcmc_sampler = MCMCSampler( sampler_state=sampler_state, thermodynamic_state=thermodynamic_state, ncfile=self.ncfile) self.mcmc_sampler.pdbfile = open('output.pdb', 'w') self.mcmc_sampler.topology = self.topology self.mcmc_sampler.timestep = timestep self.mcmc_sampler.collision_rate = 1.0 / (100 * timestep) self.mcmc_sampler.nsteps = 1000 self.mcmc_sampler.verbose = True self.exen_sampler = ExpandedEnsembleSampler(self.mcmc_sampler, self.thermodynamic_states) self.exen_sampler.verbose = True self.sams_sampler = SAMSSampler(self.exen_sampler, update_stages='two-stage', update_method='optimal') self.sams_sampler.verbose = True
def _automatic_parameter_selection(self, positions, receptor_atoms, ligand_atoms): """ Determine parameters and restrained atoms automatically, rejecting choices where standard state correction will be incorrectly computed. Parameters ---------- positions : simtk.unit.Quantity of natoms x 3 with units compatible with nanometers Reference positions to use for imposing restraints receptor_atoms : list of int A complete list of receptor atoms ligand_atoms : list of int A complete list of ligand atoms """ NSIGMA = 4 temperature = 300 * unit.kelvin kT = kB * temperature attempt = 0 MAX_ATTEMPTS = 100 reject = True logger.debug('Automatically selecting restraint atoms and parameters:') while reject and attempt < MAX_ATTEMPTS: logger.debug('Attempt %d / %d at automatically selecting atoms and restraint parameters...' % (attempt, MAX_ATTEMPTS)) # Select atoms to be used in restraint. self._restraint_atoms = self._select_restraint_atoms(positions, receptor_atoms, ligand_atoms) # Determine restraint parameters self._determine_restraint_parameters() # Terminate if we satisfy criteria reject = False for name in ['A', 'B']: theta0 = self._parameters['theta_' + name + '0'] K = self._parameters['K_theta' + name] sigma = unit.sqrt(NSIGMA * kT / (K/2.)) if (theta0 < sigma) or (theta0 > (np.pi*unit.radians - sigma)): logger.debug('Reject because theta_' + name + '0 is too close to 0 or pi for standard state correction to be accurate.') reject = True r0 = self._parameters['r_aA0'] K = self._parameters['K_r'] sigma = unit.sqrt(NSIGMA * kT / (K/2.)) if (r0 < sigma): logger.debug('Reject because r_aA0 is too close to 0 for standard state correction to be accurate.') reject = True attempt += 1
def __init__(self, sigma1=5.0 * unit.angstrom, sigma2=10.0 * unit.angstrom, temperature=300.0 * unit.kelvin, zeta=(0.0, 0.0), mass=39.948 * unit.amu, collision_rate=5.0 / unit.picosecond, platform_name='CPU'): """ Initialize the 3D harmonic oscillator with two force constants """ if len(zeta) != 2: raise Exception( 'zeta must be a list or tuple of floats with length 2') # System specific self.mass = mass self.temperature = temperature self.kT = kB * self.temperature self.kT_unitless = strip_in_unit_system(self.kT) self.platform_name = platform_name # Force Constants K1 = (self.kT / sigma1**2).in_unit_system(unit.md_unit_system) K2 = (self.kT / sigma2**2).in_unit_system(unit.md_unit_system) self.K = (K1, K2) self.zeta = (zeta[0], zeta[1]) self.sigma = (strip_in_unit_system(sigma1), strip_in_unit_system(sigma2)) # Choose the initial force constant of the harmonic oscillator self.state = np.random.choice((0, 1)) self.K_current = self.K[self.state] self.zeta_current = self.zeta[self.state] self.sigma_current = self.sigma[self.state] # self.K_current = np.random.choice((self.K1, self.K2)) # self.K_current_unitless = strip_in_unit_system(self.K_current) # Integrator specific self.collision_rate = collision_rate tau = 2 * np.pi * unit.sqrt(self.mass / (K1 / 2.0 + K2 / 2.0)) # time constant self.timestep = tau / 20.0 # The initial configuration of the oscillator self.position = np.zeros([1, 3], np.float32) self.velocity = None # The openmm system (self.context, self.integrator, self.system) = self.make_harmonic_context(self.K_current) # The sampling statistics self.state_counter = 0 self.nmoves = 0 self.radii = []
def end_to_end_CA_distance(self, topology, positions): residues = list(topology.residues()) # get the index of the first and last alpha carbons i1 = [a.index for a in residues[0].atoms() if a.name == 'CA'][0] i2 = [a.index for a in residues[-1].atoms() if a.name == 'CA'][0] # get the current distanc be between the two alpha carbons return i1, i2, sqrt(sum((positions[i1] - positions[i2])**2))
def check_harmonic_oscillator_ncmc(ncmc_nsteps=50, ncmc_integrator="VV"): """ Test NCMC switching of a 3D harmonic oscillator. In this test, the oscillator center is dragged in space, and we check the computed free energy difference with BAR, which should be 0. """ # Parameters for 3D harmonic oscillator mass = 39.948 * unit.amu # mass of particle (argon) sigma = 5.0 * unit.angstrom # standard deviation of harmonic oscillator collision_rate = 5.0/unit.picosecond # collision rate temperature = 300.0 * unit.kelvin # temperature platform_name = 'Reference' # platform anme NSIGMA_MAX = 6.0 # number of standard errors away from analytical solution tolerated before Exception is thrown # Compute derived quantities. kT = kB * temperature # thermal energy beta = 1.0 / kT # inverse energy K = kT / sigma**2 # spring constant tau = 2 * math.pi * unit.sqrt(mass/K) # time constant timestep = tau / 20.0 platform = openmm.Platform.getPlatformByName(platform_name) # Create a 3D harmonic oscillator with context parameter controlling center of oscillator. system = openmm.System() system.addParticle(mass) energy_expression = '(K/2.0) * ((x-x0)^2 + y^2 + z^2);' force = openmm.CustomExternalForce(energy_expression) force.addGlobalParameter('K', K.in_unit_system(unit.md_unit_system)) force.addGlobalParameter('x0', 0.0) force.addParticle(0, []) system.addForce(force) # Set the positions at the origin. positions = unit.Quantity(np.zeros([1, 3], np.float32), unit.angstroms) functions = { 'x0' : 'lambda' } # drag spring center x0 from perses.annihilation import NCMCVVAlchemicalIntegrator, NCMCGHMCAlchemicalIntegrator if ncmc_integrator=="VV": ncmc_insert = NCMCVVAlchemicalIntegrator(temperature, system, functions, direction='insert', nsteps=ncmc_nsteps, timestep=timestep) # 'insert' drags lambda from 0 -> 1 ncmc_delete = NCMCVVAlchemicalIntegrator(temperature, system, functions, direction='delete', nsteps=ncmc_nsteps, timestep=timestep) # 'insert' drags lambda from 0 -> 1 elif ncmc_integrator=="GHMC": ncmc_insert = NCMCGHMCAlchemicalIntegrator(temperature, system, functions, direction='insert', collision_rate=9.1/unit.picoseconds, nsteps=ncmc_nsteps, timestep=timestep) # 'insert' drags lambda from 0 -> 1 ncmc_delete = NCMCGHMCAlchemicalIntegrator(temperature, system, functions, direction='delete', collision_rate=9.1/unit.picoseconds, nsteps=ncmc_nsteps, timestep=timestep) # 'insert' drags lambda from 0 -> 1 else: raise Exception("%s not recognized as integrator name. Options are VV and GHMC" % ncmc_integrator) # Run NCMC switching trials where the spring center is switched with lambda: 0 -> 1 over a finite number of steps. w_f = collect_switching_data(system, positions, functions, temperature, collision_rate, timestep, platform, ncmc_integrator=ncmc_insert, ncmc_nsteps=ncmc_nsteps, direction='insert') w_r = collect_switching_data(system, positions, functions, temperature, collision_rate, timestep, platform, ncmc_integrator=ncmc_delete, ncmc_nsteps=ncmc_nsteps, direction='delete') from pymbar import BAR [df, ddf] = BAR(w_f, w_r, method='self-consistent-iteration') print('%8.3f +- %.3f kT' % (df, ddf)) if (abs(df) > NSIGMA_MAX * ddf): msg = 'Delta F (%d steps switching) = %f +- %f kT; should be within %f sigma of 0' % (ncmc_nsteps, df, ddf, NSIGMA_MAX) msg += '\n' msg += 'w_f = %s\n' % str(w_f) msg += 'w_r = %s\n' % str(w_r) raise Exception(msg)
def computeHarmonicOscillatorExpectations(K, mass, temperature): """ Compute mean and variance of potential and kinetic energies for a 3D harmonic oscillator. NOTES Numerical quadrature is used to compute the mean and standard deviation of the potential energy. Mean and standard deviation of the kinetic energy, as well as the absolute free energy, is computed analytically. ARGUMENTS K (simtk.unit.Quantity) - spring constant mass (simtk.unit.Quantity) - mass of particle temperature (simtk.unit.Quantity) - temperature RETURNS values (dict) """ values = dict() # Compute thermal energy and inverse temperature from specified temperature. kB = units.BOLTZMANN_CONSTANT_kB * units.AVOGADRO_CONSTANT_NA kT = kB * temperature # thermal energy beta = 1.0 / kT # inverse temperature # Compute standard deviation along one dimension. sigma = 1.0 / units.sqrt(beta * K) # Define limits of integration along r. r_min = 0.0 * units.nanometers # initial value for integration r_max = 10.0 * sigma # maximum radius to integrate to # Compute mean and std dev of potential energy. V = lambda r : (K/2.0) * (r*units.nanometers)**2 / units.kilojoules_per_mole # potential in kJ/mol, where r in nm q = lambda r : 4.0 * math.pi * r**2 * math.exp(-beta * (K/2.0) * (r*units.nanometers)**2) # q(r), where r in nm (IqV2, dIqV2) = scipy.integrate.quad(lambda r : q(r) * V(r)**2, r_min / units.nanometers, r_max / units.nanometers) (IqV, dIqV) = scipy.integrate.quad(lambda r : q(r) * V(r), r_min / units.nanometers, r_max / units.nanometers) (Iq, dIq) = scipy.integrate.quad(lambda r : q(r), r_min / units.nanometers, r_max / units.nanometers) values['potential'] = dict() values['potential']['mean'] = (IqV / Iq) * units.kilojoules_per_mole values['potential']['stddev'] = (IqV2 / Iq) * units.kilojoules_per_mole # Compute mean and std dev of kinetic energy. values['kinetic'] = dict() values['kinetic']['mean'] = (3./2.) * kT values['kinetic']['stddev'] = math.sqrt(3./2.) * kT # Compute dimensionless free energy. # f = - \ln \int_{-\infty}^{+\infty} \exp[-\beta K x^2 / 2] # = - \ln \int_{-\infty}^{+\infty} \exp[-x^2 / 2 \sigma^2] # = - \ln [\sqrt{2 \pi} \sigma] values['f'] = - numpy.log(2 * numpy.pi * (sigma / units.angstroms)**2) * (3.0/2.0) return values
def computeHarmonicOscillatorExpectations(K, mass, temperature): """ Compute mean and variance of potential and kinetic energies for harmonic oscillator. Numerical quadrature is used. ARGUMENTS K - spring constant mass - mass of particle temperature - temperature RETURNS values """ values = dict() # Compute thermal energy and inverse temperature from specified temperature. kB = units.BOLTZMANN_CONSTANT_kB * units.AVOGADRO_CONSTANT_NA kT = kB * temperature # thermal energy beta = 1.0 / kT # inverse temperature # Compute standard deviation along one dimension. sigma = 1.0 / units.sqrt(beta * K) # Define limits of integration along r. r_min = 0.0 * units.nanometers # initial value for integration r_max = 10.0 * sigma # maximum radius to integrate to # Compute mean and std dev of potential energy. V = lambda r: (K / 2.0) * ( r * units.nanometers )**2 / units.kilojoules_per_mole # potential in kJ/mol, where r in nm q = lambda r: 4.0 * math.pi * r**2 * math.exp(-beta * (K / 2.0) * ( r * units.nanometers)**2) # q(r), where r in nm (IqV2, dIqV2) = scipy.integrate.quad(lambda r: q(r) * V(r)**2, r_min / units.nanometers, r_max / units.nanometers) (IqV, dIqV) = scipy.integrate.quad(lambda r: q(r) * V(r), r_min / units.nanometers, r_max / units.nanometers) (Iq, dIq) = scipy.integrate.quad(lambda r: q(r), r_min / units.nanometers, r_max / units.nanometers) values['potential'] = dict() values['potential']['mean'] = (IqV / Iq) * units.kilojoules_per_mole values['potential']['stddev'] = (IqV2 / Iq) * units.kilojoules_per_mole # Compute mean and std dev of kinetic energy. values['kinetic'] = dict() values['kinetic']['mean'] = (3. / 2.) * kT values['kinetic']['stddev'] = math.sqrt(3. / 2.) * kT return values
def get_harmonic_testsystem(temperature=300 * unit.kelvin, sigma=1.0 * unit.angstroms, mass=39.948 * unit.amus): """ simple function to generate a tractable Harmonic Oscillator testsystem arguments temperature : float * unit.kelvin (or any temperature unit) temperature of oscillator sigma : float * unit.angstroms (or any length unit) the standard deviation of the Harmonic Oscillator mass : float * unit.amus (or any mass unit) reduced mass of the particles returns testsystem : openmmtools.testsystems.HarmonicOscillator test system to return period : float * unit.picoseconds (or any time unit) period of oscillator collision_rate : float / unit.picoseconds (or any time unit) collision rate of oscillator timestep : float * unit.picoseconds (or any time unit) timestep of the oscillator for MD alchemical_functions : dict dict of alchemical functions; {<name of alchemical parameter>: lepton-readable function} """ from openmmtools.testsystems import HarmonicOscillator #define parameters for the harmonic oscillator kT = kB * temperature beta = 1. / kT K = kT / sigma**2 period = unit.sqrt(mass / K) timestep = period / 20. collision_rate = 1. / period #define some alchemical parameters parameters = dict() parameters['testsystems_HarmonicOscillator_x0'] = (0 * sigma, 2 * sigma) parameters['testsystems_HarmonicOscillator_U0'] = (0 * kT, 1 * kT) lambda_name = 'fractional_iteration' alchemical_functions = { name: f'(1-{lambda_name})*{value[0].value_in_unit_system(unit.md_unit_system)} + {lambda_name}*{value[1].value_in_unit_system(unit.md_unit_system)}' for (name, value) in parameters.items() } testsystem = HarmonicOscillator(K=K, mass=mass) system = testsystem.system positions = testsystem.positions return testsystem, period, collision_rate, timestep, alchemical_functions
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 computeHarmonicOscillatorExpectations(K, mass, temperature): """ Compute mean and variance of potential and kinetic energies for harmonic oscillator. Numerical quadrature is used. ARGUMENTS K - spring constant mass - mass of particle temperature - temperature RETURNS values """ values = dict() # Compute thermal energy and inverse temperature from specified temperature. kB = units.BOLTZMANN_CONSTANT_kB * units.AVOGADRO_CONSTANT_NA kT = kB * temperature # thermal energy beta = 1.0 / kT # inverse temperature # Compute standard deviation along one dimension. sigma = 1.0 / units.sqrt(beta * K) # Define limits of integration along r. r_min = 0.0 * units.nanometers # initial value for integration r_max = 10.0 * sigma # maximum radius to integrate to # Compute mean and std dev of potential energy. V = lambda r : (K/2.0) * (r*units.nanometers)**2 / units.kilojoules_per_mole # potential in kJ/mol, where r in nm q = lambda r : 4.0 * math.pi * r**2 * math.exp(-beta * (K/2.0) * (r*units.nanometers)**2) # q(r), where r in nm (IqV2, dIqV2) = scipy.integrate.quad(lambda r : q(r) * V(r)**2, r_min / units.nanometers, r_max / units.nanometers) (IqV, dIqV) = scipy.integrate.quad(lambda r : q(r) * V(r), r_min / units.nanometers, r_max / units.nanometers) (Iq, dIq) = scipy.integrate.quad(lambda r : q(r), r_min / units.nanometers, r_max / units.nanometers) values['potential'] = dict() values['potential']['mean'] = (IqV / Iq) * units.kilojoules_per_mole values['potential']['stddev'] = (IqV2 / Iq) * units.kilojoules_per_mole # Compute mean and std dev of kinetic energy. values['kinetic'] = dict() values['kinetic']['mean'] = (3./2.) * kT values['kinetic']['stddev'] = math.sqrt(3./2.) * kT return values
def __init__(self, forceField='amber14-all', water=None, boxLength=25*unit.angstroms, bareSystem=False): pdb = app.PDBFile(os.path.join(afed.__path__[0], 'data', 'alanine-dipeptide.pdb')) if water is None: force_field = app.ForceField(f'{forceField}.xml') self._topology = pdb.topology self._positions = pdb.positions else: force_field = app.ForceField(f'{forceField}.xml', f'{water}.xml') modeller = app.Modeller(pdb.topology, pdb.positions) modeller.addSolvent(force_field, model=water, boxSize=boxLength*openmm.Vec3(1, 1, 1)) self._topology = modeller.topology self._positions = modeller.positions self._system = force_field.createSystem( self._topology, nonbondedMethod=app.NoCutoff if water is None else app.PME, constraints=None, rigidWater=False, removeCMMotion=False, ) if bareSystem: return atoms = [(a.name, a.residue.name) for a in self._topology.atoms()] psi_atoms = [('N', 'ALA'), ('CA', 'ALA'), ('C', 'ALA'), ('N', 'NME')] self._psi_angle = openmm.CustomTorsionForce('theta') self._psi_angle.addTorsion(*[atoms.index(i) for i in psi_atoms], []) phi_atoms = [('C', 'ACE'), ('N', 'ALA'), ('CA', 'ALA'), ('C', 'ALA')] self._phi_angle = openmm.CustomTorsionForce('theta') self._phi_angle.addTorsion(*[atoms.index(i) for i in phi_atoms], []) period = 360*unit.degrees self._psi = afed.DrivenCollectiveVariable('psi', self._psi_angle, unit.radians, period) self._phi = afed.DrivenCollectiveVariable('phi', self._phi_angle, unit.radians, period) value = 180*unit.degrees minval = -value maxval = value T = 1500*unit.kelvin mass = 168.0*unit.dalton*(unit.angstroms/unit.radian)**2 velocity_scale = unit.sqrt(unit.BOLTZMANN_CONSTANT_kB*unit.AVOGADRO_CONSTANT_NA*T/mass) self._psi_driver = afed.DriverParameter('psi_s', unit.radians, value, T, velocity_scale, minval, maxval, periodic=True) self._phi_driver = afed.DriverParameter('phi_s', unit.radians, value, T, velocity_scale, minval, maxval, periodic=True) self._driving_force = afed.HarmonicDrivingForce() K = 2.78E3*unit.kilocalories_per_mole/unit.radians**2 self._driving_force.addPair(self._psi, self._psi_driver, K) self._driving_force.addPair(self._phi, self._phi_driver, K) self._system.addForce(self._driving_force)
def __init__(self, **kwargs): super(HarmonicOscillatorSimulatedTempering, self).__init__(**kwargs) self.description = 'Harmonic oscillator simulated tempering simulation' # Create topology, positions, and system. from openmmtools.testsystems import HarmonicOscillator K = 1.0 * unit.kilocalories_per_mole / unit.angstroms**2 # 3D harmonic oscillator spring constant mass = 39.948 * unit.amu # 3D harmonic oscillator particle mass period = 2.0 * np.pi * unit.sqrt(mass / K) # harmonic oscillator period timestep = 0.01 * period testsystem = HarmonicOscillator(K=K, mass=mass) self.topology = testsystem.topology self.positions = testsystem.positions self.system = testsystem.system # Create thermodynamic states. Tmin = 100 * unit.kelvin Tmax = 1000 * unit.kelvin ntemps = 8 # number of temperatures from sams import ThermodynamicState temperatures = unit.Quantity(np.logspace(np.log10(Tmin / unit.kelvin), np.log10(Tmax / unit.kelvin), ntemps), unit.kelvin) self.thermodynamic_states = [ ThermodynamicState(system=self.system, temperature=temperature) for temperature in temperatures ] # Compute analytical logZ for each thermodynamic state. self.logZ = np.zeros([ntemps], np.float64) for (index, thermodynamic_state) in enumerate(self.thermodynamic_states): beta = thermodynamic_state.beta self.logZ[index] = - 1.5 * np.log(beta * K * unit.angstrom**2) self.logZ[:] -= self.logZ[0] # Create SAMS samplers from sams.samplers import SamplerState, MCMCSampler, ExpandedEnsembleSampler, SAMSSampler thermodynamic_state_index = 0 # initial thermodynamic state index thermodynamic_state = self.thermodynamic_states[thermodynamic_state_index] sampler_state = SamplerState(positions=self.positions) self.mcmc_sampler = MCMCSampler(sampler_state=sampler_state, thermodynamic_state=thermodynamic_state, ncfile=self.ncfile) self.mcmc_sampler.pdbfile = open('output.pdb', 'w') self.mcmc_sampler.topology = self.topology self.mcmc_sampler.timestep = timestep self.mcmc_sampler.collision_rate = 1.0 / (100 * timestep) self.mcmc_sampler.nsteps = 1000 self.mcmc_sampler.verbose = True self.exen_sampler = ExpandedEnsembleSampler(self.mcmc_sampler, self.thermodynamic_states) self.exen_sampler.verbose = True self.sams_sampler = SAMSSampler(self.exen_sampler, update_stages='two-stage', update_method='optimal') self.sams_sampler.verbose = True
def get14Interactions(self): """Return list of atom pairs, chargeProduct, rMin and epsilon for each 1-4 interaction""" dihedralPointers = self._raw_data["DIHEDRALS_INC_HYDROGEN"] \ +self._raw_data["DIHEDRALS_WITHOUT_HYDROGEN"] returnList=[] charges=self.getCharges() nonbondTerms = self.getNonbondTerms() for ii in range(0,len(dihedralPointers),5): if int(dihedralPointers[ii+2])>0 and int(dihedralPointers[ii+3])>0: iAtom = int(dihedralPointers[ii])/3 lAtom = int(dihedralPointers[ii+3])/3 chargeProd = charges[iAtom]*charges[lAtom] (rVdwI, epsilonI) = nonbondTerms[iAtom] (rVdwL, epsilonL) = nonbondTerms[lAtom] rMin = (rVdwI+rVdwL) epsilon = units.sqrt(epsilonI*epsilonL) returnList.append((iAtom, lAtom, chargeProd, rMin, epsilon)) return returnList
def generateMaxwellBoltzmannVelocities(system, temperature): """Generate Maxwell-Boltzmann velocities. ARGUMENTS system (simtk.openmm.System) - the system for which velocities are to be assigned temperature (simtk.unit.Quantity of temperature) - the temperature at which velocities are to be assigned RETURNS velocities (simtk.unit.Quantity of numpy Nx3 array, units length/time) - particle velocities TODO This could be sped up by introducing vector operations. """ # Get number of atoms natoms = system.getNumParticles() # Create storage for velocities. velocities = units.Quantity( numpy.zeros([natoms, 3], numpy.float32), units.nanometer / units.picosecond ) # velocities[i,k] is the kth component of the velocity of atom i # Compute thermal energy and inverse temperature from specified temperature. kB = units.BOLTZMANN_CONSTANT_kB * units.AVOGADRO_CONSTANT_NA kT = kB * temperature # thermal energy beta = 1.0 / kT # inverse temperature # Assign velocities from the Maxwell-Boltzmann distribution. for atom_index in range(natoms): mass = system.getParticleMass(atom_index) # atomic mass sigma = units.sqrt( kT / mass ) # standard deviation of velocity distribution for each coordinate for this atom for k in range(3): velocities[atom_index, k] = sigma * numpy.random.normal() # Return velocities return velocities
def _assign_Maxwell_Boltzmann_velocities(self, system, temperature): """Generate Maxwell-Boltzmann velocities. @param system the system for which velocities are to be assigned @type simtk.chem.openmm.System or System @param temperature the temperature at which velocities are to be assigned @type Quantity with units of temperature @return velocities drawn from the Maxwell-Boltzmann distribution at the appropriate temperature @returntype (natoms x 3) numpy array wrapped in Quantity with units of velocity TODO This could be sped up by introducing vector operations. """ # Get number of atoms natoms = system.getNumParticles() # Create storage for velocities. velocities = units.Quantity( numpy.zeros([natoms, 3], numpy.float32), units.nanometer / units.picosecond ) # velocities[i,k] is the kth component of the velocity of atom i # Compute thermal energy and inverse temperature from specified temperature. kT = kB * temperature # thermal energy beta = 1.0 / kT # inverse temperature # Assign velocities from the Maxwell-Boltzmann distribution. for atom_index in range(natoms): mass = system.getParticleMass(atom_index) # atomic mass sigma = units.sqrt( kT / mass ) # standard deviation of velocity distribution for each coordinate for this atom for k in range(3): velocities[atom_index, k] = sigma * numpy.random.normal() # Return velocities return velocities
def generateMaxwellBoltzmannVelocities(system, temperature): """Generate Maxwell-Boltzmann velocities. ARGUMENTS system (simtk.openmm.System) - the system for which velocities are to be assigned temperature (simtk.unit.Quantity of temperature) - the temperature at which velocities are to be assigned RETURNS velocities (simtk.unit.Quantity of numpy Nx3 array, units length/time) - particle velocities TODO This could be sped up by introducing vector operations. """ # Get number of atoms natoms = system.getNumParticles() # Create storage for velocities. velocities = units.Quantity(numpy.zeros([natoms, 3], numpy.float32), units.nanometer / units.picosecond) # velocities[i,k] is the kth component of the velocity of atom i # Compute thermal energy and inverse temperature from specified temperature. kB = units.BOLTZMANN_CONSTANT_kB * units.AVOGADRO_CONSTANT_NA kT = kB * temperature # thermal energy beta = 1.0 / kT # inverse temperature # Assign velocities from the Maxwell-Boltzmann distribution. for atom_index in range(natoms): mass = system.getParticleMass(atom_index) # atomic mass sigma = units.sqrt(kT / mass) # standard deviation of velocity distribution for each coordinate for this atom for k in range(3): velocities[atom_index,k] = sigma * numpy.random.normal() # Return velocities return velocities
def _update_nonbonded_force(self, group, nbforce, parameters, pbc_for_exceptions): internal_exception_pairs = [] for index in range(nbforce.getNumExceptions()): i, j, _, _, epsilon = nbforce.getExceptionParameters(index) i_in_group, j_in_group = i in group, j in group if i_in_group and j_in_group: internal_exception_pairs.append(set([i, j])) elif (i_in_group or j_in_group): raise ValueError( "No exceptions are allowed for in-group/out-group interactions" ) for i, j in itertools.combinations(group, 2): if set([i, j]) not in internal_exception_pairs: chargeprod = parameters[i].charge * parameters[j].charge sigma = (parameters[i].sigma + parameters[j].sigma) / 2 epsilon = unit.sqrt(parameters[i].epsilon * parameters[j].epsilon) nbforce.addException(i, j, chargeprod, sigma, epsilon) if pbc_for_exceptions: nbforce.setExceptionsUsePeriodicBoundaryConditions(True)
def _assign_Maxwell_Boltzmann_velocities(self, system, temperature): """Generate Maxwell-Boltzmann velocities. @param system the system for which velocities are to be assigned @type simtk.chem.openmm.System or System @param temperature the temperature at which velocities are to be assigned @type Quantity with units of temperature @return velocities drawn from the Maxwell-Boltzmann distribution at the appropriate temperature @returntype (natoms x 3) numpy array wrapped in Quantity with units of velocity TODO This could be sped up by introducing vector operations. """ # Get number of atoms natoms = system.getNumParticles() # Create storage for velocities. velocities = units.Quantity(numpy.zeros([natoms, 3], numpy.float32), units.nanometer / units.picosecond) # velocities[i,k] is the kth component of the velocity of atom i # Compute thermal energy and inverse temperature from specified temperature. kT = kB * temperature # thermal energy beta = 1.0 / kT # inverse temperature # Assign velocities from the Maxwell-Boltzmann distribution. for atom_index in range(natoms): mass = system.getParticleMass(atom_index) # atomic mass sigma = units.sqrt(kT / mass) # standard deviation of velocity distribution for each coordinate for this atom for k in range(3): velocities[atom_index,k] = sigma * numpy.random.normal() # Return velocities return velocities
def createMergedTopology(self, alchemical_lambda, mm=None, verbose=False): """ Create a merged topology file with the specified alchemical lambda value for interpolating between molecules A and B. ARGUMENTS alchemical_lambda (float) - the alchemical lambda in interval [0,1] for interpolating between molecule A (alchemical_lambda = 0) and molecule B (alchemical_lambda = 1), OPTIONAL ARGUMENTS mm (implements simtk.openmm interface) - OpenMM API implementation to use (default: simtk.openmm) TODO EXAMPLES NOTES Merged molecule will contain atom groups in this order: S_AB : [atoms in A and B] S_A : [atoms in A and not B] S_B : [atoms in B and not A] Masses in S_AB are the geometric product of their masses in A and B. """ # Record timing statistics. if verbose: print "Creating merged topology corresponding to alchemical lamdba of %f..." % alchemical_lambda # Get local references to systems A and B. system_A = self.system_A system_B = self.system_B corresponding_atoms = self.corresponding_atoms # # Construct atom sets and correspondence lists for A, B, and merged topology. # # Determine number of atoms in each system. natoms_A = system_A.getNumParticles() # number of atoms in molecule A natoms_B = system_B.getNumParticles() # number of atoms in molecule B natoms_AandB = len(corresponding_atoms) # number of atoms in both A and B natoms_AnotB = natoms_A - natoms_AandB # number of atoms in A and not B natoms_BnotA = natoms_B - natoms_AandB # number of atoms in B and not A natoms_merged = natoms_AandB + natoms_AnotB + natoms_BnotA # number of atoms in merged topology # Determine sets of atoms shared and not shared. atomset_A_AandB = set([ index_A for (index_A, index_B) in corresponding_atoms ]) # atoms in molecule A and B (A molecule numbering) atomset_A_AnotB = set(range(natoms_A)) - atomset_A_AandB # atoms in molecule A and not B (A molecule numbering) atomset_A_BnotA = set() atomset_B_AandB = set([ index_B for (index_A, index_B) in corresponding_atoms ]) # atoms in molecule B and A (B molecule numbering) atomset_B_BnotA = set(range(natoms_B)) - atomset_B_AandB # atoms in molecule B and not A (B molecule numbering) atomset_B_AnotB = set() atomset_merged_AandB = set(range(natoms_AandB)) # atoms present in A and B (merged molecule numbering) atomset_merged_AnotB = set(range(natoms_AandB, natoms_AandB + natoms_AnotB)) # atoms present in A and not B (merged molcule numbering) atomset_merged_BnotA = set(range(natoms_AandB + natoms_AnotB, natoms_AandB + natoms_AnotB + natomsBnotA)) # atoms present in B and not A (merged molecule numbering) # Construct lists of corresponding atom indices. atom_index = dict() # # Construct merged OpenMM system. # import simtk.unit as units # Select OpenMM API implementation to use. if not mm: import simtk.openmm mm = simtk.openmm # Create new System object. system = mm.System() # Populate merged sytem with atoms. # Masses of atoms in both A and B are geometric mean; otherwise standard mass. # Add particles in A and B. for (index_A, index_B) in corresponding_atoms: # Create particle with geometric mean of masses. mass_A = system_A.getParticleMass(index_A) mass_B = system_B.getParticleMass(index_B) mass = units.sqrt(mass_A * mass_B) system.addParticle(mass) for index_A in atomlist_A_AnotB: mass_A = system_A.getParticleMass(index_A) system.addParticle(mass_A) for index_B in atomlist_B_BnotA: mass_B = system_B.getParticleMass(index_B) system.addParticle(mass_B) # Define helper function. def find_force(system, classname): """ Find the specified Force object in an OpenMM System by classname. ARGUMENTS system (simtk.openmm.System) - system containing forces to be searched classname (string) - classname of Force object to locate RETURNS force (simtk.openmm.Force) - the first Force object encountered with the specified classname, or None if one could not be found """ nforces = system.getNumForces() force = None for index in range(nforces): if isinstance(system.getForce(index), getattr(mm, classname)): force = system.getForce(index) return force # Add bonds. # NOTE: This does not currently deal with bonds that are broken or formed during the transformation. force_A = find_force(system_A, 'HarmonicBondForce') for index in range(force_A.getNumBonds()): # Get bond parameters from molecule A. [iatom_A, jatom_A, length_A, k_A] = force.getBondParameters(index) # Translate atom indices to merged atom indices. (iatom_merged, jatom_merged) = (atom_indices['A'][iatom_A]['merged'], atom_indices['B'][jatom_A]['merged']) # Store bond parameters for random access. bonds[(iatom_merged, jatom_merged)] = (length-A, k_A) force_B = find_force(system_B, 'HarmonicBondForce') for index in range(force_B.getNumBonds()): # Get bond parameters from molecule A. [iatom_B, jatom_B, length_B, k_B] = force.getBondParameters(index) # Translate atom indices to merged atom indices. (iatom_merged, jatom_merged) = (atom_indices['B'][iatom_B]['merged'], atom_indices['B'][jatom_B]['merged']) # Store bond parameters for random access. if (iatom_merged, jatom_merged) in bonds: # Mix bonds. (length_A, k_A) = bonds[(iatom_merged, jatom_merged)] (length, k) = ( (1.0-alchemical_lambda)*length_A + alchemical_lambda*length_B, (1.0-alchemical_lambda)*k_A + alchemical_lambda*k_B ) bonds[(iatom_merged, jatom_merged)] = (length, k) else: bonds[(iatom_merged, jatom_merged)] = (length_B, k_B) # Add bonds to merged topology. force = mm.HarmonicBondForce() for (iatom, jatom) in bonds: # Retrieve bond parameters. (length, j) = bonds[(iatom, jatom)] # Add bond. force.addBond(iatom, jatom, length, k) # Add the Force to the merged topology sytem. system.addForce(force)
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) atomConstraints[c[0]].append((c[1], c[2])) atomConstraints[c[1]].append((c[0], c[2])) 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 = units.sqrt(l1*l1 + l2*l2 - 2*l1*l2*units.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*units.elementary_charge**2, 1.0*units.angstroms, 0.0*units.kilocalories_per_mole) 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]/units.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]/units.sqrt(distOH[res]**2-(0.5*distHH[res])**2) angleHOH = 2*math.asin(0.5*distHH[res]/distOH[res]) lenCross = (distOH[res].value_in_unit(units.nanometer)**2)*math.sin(angleHOH)*units.nanometer 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") # TODO: Add GBVI terms? return system
# ntrials = integrator.getGlobalVariable(global_variables['ntrials']) # print "accepted %d / %d (%.3f %%)" % (naccept, ntrials, float(naccept)/float(ntrials)*100.0) # Accumulate statistics. x_n[iteration] = state.getPositions(asNumpy=True)[0,0] / units.angstroms potential_n[iteration] = final_potential_energy / kT kinetic_n[iteration] = final_kinetic_energy / kT temperature_n[iteration] = instantaneous_temperature / units.kelvin delta_n[iteration] = delta_total_energy / kT # Compute expected statistics for harmonic oscillator. K = 100.0 * units.kilocalories_per_mole / units.angstroms**2 beta = 1.0 / kT x_mean_exact = 0.0 # mean, in angstroms x_std_exact = 1.0 / units.sqrt(beta * K) / units.angstroms # std dev, in angstroms # Analyze statistics. g = statisticalInefficiency(potential_n) Neff = niterations / g # number of effective samples x_mean = x_n.mean() dx_mean = x_n.std() / numpy.sqrt(Neff) x_mean_error = x_mean - x_mean_exact x_var = x_n.var() dx_var = x_var * numpy.sqrt(2. / (Neff-1)) x_std = x_n.std() dx_std = 0.5 * dx_var / x_std x_std_error = x_std - x_std_exact
def build_softcore_system(reference_system, receptor_atoms, ligand_atoms, valence_lambda, coulomb_lambda, vdw_lambda, annihilate=True): """ Build alchemically-modified system where ligand is decoupled or annihilated using *SoftcoreForce classes. """ # Create new system. system = openmm.System() # Set periodic box vectors. [a,b,c] = reference_system.getDefaultPeriodicBoxVectors() system.setDefaultPeriodicBoxVectors(a,b,c) # Add atoms. for atom_index in range(reference_system.getNumParticles()): mass = reference_system.getParticleMass(atom_index) system.addParticle(mass) # Add constraints for constraint_index in range(reference_system.getNumConstraints()): [iatom, jatom, r0] = reference_system.getConstraintParameters(constraint_index) system.addConstraint(iatom, jatom, r0) # Perturb force terms. for force_index in range(reference_system.getNumForces()): reference_force = reference_system.getForce(force_index) # Dispatch forces if isinstance(reference_force, openmm.HarmonicBondForce): # HarmonicBondForce force = openmm.HarmonicBondForce() for bond_index in range(reference_force.getNumBonds()): # Retrieve parameters. [iatom, jatom, r0, K] = reference_force.getBondParameters(bond_index) # Annihilate if directed. if annihilate and (iatom in ligand_atoms) and (jatom in ligand_atoms): K *= valence_lambda # Add bond parameters. force.addBond(iatom, jatom, r0, K) # Add force to new system. system.addForce(force) elif isinstance(reference_force, openmm.HarmonicAngleForce): # HarmonicAngleForce force = openmm.HarmonicAngleForce() for angle_index in range(reference_force.getNumAngles()): # Retrieve parameters. [iatom, jatom, katom, theta0, Ktheta] = reference_force.getAngleParameters(angle_index) # Annihilate if directed: if annihilate and (iatom in ligand_atoms) and (jatom in ligand_atoms) and (katom in ligand_atoms): Ktheta *= valence_lambda # Add parameters. force.addAngle(iatom, jatom, katom, theta0, Ktheta) # Add force to system. system.addForce(force) elif isinstance(reference_force, openmm.PeriodicTorsionForce): # PeriodicTorsionForce force = openmm.PeriodicTorsionForce() for torsion_index in range(reference_force.getNumTorsions()): # Retrieve parmaeters. [particle1, particle2, particle3, particle4, periodicity, phase, k] = reference_force.getTorsionParameters(torsion_index) # Annihilate if directed: if annihilate and (particle1 in ligand_atoms) and (particle2 in ligand_atoms) and (particle3 in ligand_atoms) and (particle4 in ligand_atoms): k *= valence_lambda # Add parameters. force.addTorsion(particle1, particle2, particle3, particle4, periodicity, phase, k) # Add force to system. system.addForce(force) elif isinstance(reference_force, openmm.NonbondedForce): # NonbondedForce force = openmm.NonbondedSoftcoreForce() for particle_index in range(reference_force.getNumParticles()): # Retrieve parameters. [charge, sigma, epsilon] = reference_force.getParticleParameters(particle_index) # Alchemically modify parameters. if particle_index in ligand_atoms: charge *= coulomb_lambda epsilon *= vdw_lambda # Add modified particle parameters. force.addParticle(charge, sigma, epsilon, vdw_lambda) else: # Add unmodified particle parameters for receptor atoms. force.addParticle(charge, sigma, epsilon, 1.0) for exception_index in range(reference_force.getNumExceptions()): # Retrieve parameters. [iatom, jatom, chargeprod, sigma, epsilon] = reference_force.getExceptionParameters(exception_index) # Alchemically modify epsilon and chargeprod. if annihilate and (iatom in ligand_atoms) and (jatom in ligand_atoms): epsilon *= vdw_lambda chargeprod *= coulomb_lambda # Add modified exception parameters. force.addException(iatom, jatom, chargeprod, sigma, epsilon, vdw_lambda) else: # Add unmodified exception parameters. force.addException(iatom, jatom, chargeprod, sigma, epsilon) if not annihilate: # Build a list of exceptions. exceptions = set() for exception_index in range(reference_force.getNumExceptions()): [iatom, jatom, chargeprod, sigma, epsilon] = reference_force.getExceptionParameters(exception_index) exceptions.add( (iatom,jatom) ) exceptions.add( (jatom,iatom) ) # Add new intraligand interactions. for iatom in ligand_atoms: for jatom in ligand_atoms: if (iatom,jatom) not in exceptions: exceptions.add( (iatom,jatom) ) exceptions.add( (jatom,iatom) ) [charge1, sigma1, epsilon1] = reference_force.getParticleParameters(iatom) [charge2, sigma2, epsilon2] = reference_force.getParticleParameters(jatom) chargeprod = charge1*charge2 sigma = 0.5 * (sigma1 + sigma2) epsilon = units.sqrt(epsilon1*epsilon2) #chargeprod = (charge1/units.elementary_charge) * (charge2/units.elementary_charge) #sigma = 0.5 * (sigma1/units.nanometers + sigma2/units.nanometers) #epsilon = numpy.sqrt((epsilon1/units.kilojoules_per_mole) * (epsilon2/units.kilojoules_per_mole)) force.addException(iatom, jatom, chargeprod, sigma, epsilon) # Set parameters. force.setNonbondedMethod( reference_force.getNonbondedMethod() ) force.setCutoffDistance( reference_force.getCutoffDistance() ) force.setReactionFieldDielectric( reference_force.getReactionFieldDielectric() ) force.setEwaldErrorTolerance( reference_force.getEwaldErrorTolerance() ) # Add force to new system. system.addForce(force) elif isinstance(reference_force, openmm.GBSAOBCForce): # GBSAOBCForce force = openmm.GBSAOBCSoftcoreForce() force.setSolventDielectric( reference_force.getSolventDielectric() ) force.setSoluteDielectric( reference_force.getSoluteDielectric() ) for particle_index in range(reference_force.getNumParticles()): # Retrieve parameters. [charge, radius, scaling_factor] = reference_force.getParticleParameters(particle_index) # Alchemically modify parameters. if particle_index in ligand_atoms: # Scale charge and contribution to GB integrals. force.addParticle(charge*coulomb_lambda, radius, scaling_factor, coulomb_lambda) else: # Don't modulate GB for receptor atoms. force.addParticle(charge, radius, scaling_factor, 1.0) # Add force to new system. system.addForce(force) else: # Don't add unrecognized forces. pass return system
# Roughly corresponds to conditions from http://www.cstl.nist.gov/srs/LJ_PURE/mc.htm nparticles = 500 mass = 39.9 * unit.amu sigma = 3.4 * unit.angstrom epsilon = 0.238 * unit.kilocalories_per_mole #reduced_density = 0.860 # reduced_density = density * (sigma**3) reduced_density = 0.960 # reduced_density = density * (sigma**3) reduced_temperature = 0.850 # reduced_temperature = kB * temperature / epsilon reduced_pressure = 1.2660 # reduced_pressure = pressure * (sigma**3) / epsilon platform_name = 'CUDA' # OpenMM platform name to use for simulation platform = openmm.Platform.getPlatformByName(platform_name) data_directory = 'data' # Directory in which data is to be stored r0 = 2.0**(1./6.) * sigma # minimum potential distance for Lennard-Jones interaction characteristic_timescale = unit.sqrt((mass * r0**2) / (72 * epsilon)) # characteristic timescale for bound Lennard-Jones interaction # http://borisv.lk.net/matsc597c-1997/simulations/Lecture5/node3.html timestep = 0.01 * characteristic_timescale # integrator timestep # From http://www.cstl.nist.gov/srs/LJ_PURE/md.htm #characteristic_timescale = unit.sqrt(mass * sigma**2 / epsilon) #timestep = 0.05 * characteristic_timescale print "characteristic timescale = %.3f ps" % (characteristic_timescale / unit.picoseconds) print "timestep = %.12f ps" % (timestep / unit.picoseconds) collision_rate = 5.0 / unit.picoseconds # collision rate for Langevin thermostat barostat_frequency = 25 # number of steps between barostat updates # Set parameters for number of simulation replicates, number of iterations per simulation, and number of steps per iteration. nreplicates = 100
def createMergedTopology(self, alchemical_lambda, mm=None, verbose=False): """ Create a merged topology file with the specified alchemical lambda value for interpolating between molecules A and B. ARGUMENTS alchemical_lambda (float) - the alchemical lambda in interval [0,1] for interpolating between molecule A (alchemical_lambda = 0) and molecule B (alchemical_lambda = 1), OPTIONAL ARGUMENTS mm (implements simtk.openmm interface) - OpenMM API implementation to use (default: simtk.openmm) TODO EXAMPLES NOTES Merged molecule will contain atom groups in this order: S_AB : [atoms in A and B] S_A : [atoms in A and not B] S_B : [atoms in B and not A] Masses in S_AB are the geometric product of their masses in A and B. """ # Record timing statistics. if verbose: print "Creating merged topology corresponding to alchemical lamdba of %f..." % alchemical_lambda # Get local references to systems A and B. system_A = self.system_A system_B = self.system_B corresponding_atoms = self.corresponding_atoms # # Construct atom sets and correspondence lists for A, B, and merged topology. # # Determine number of atoms in each system. natoms_A = system_A.getNumParticles() # number of atoms in molecule A natoms_B = system_B.getNumParticles() # number of atoms in molecule B natoms_AandB = len( corresponding_atoms) # number of atoms in both A and B natoms_AnotB = natoms_A - natoms_AandB # number of atoms in A and not B natoms_BnotA = natoms_B - natoms_AandB # number of atoms in B and not A natoms_merged = natoms_AandB + natoms_AnotB + natoms_BnotA # number of atoms in merged topology # Determine sets of atoms shared and not shared. atomset_A_AandB = set([ index_A for (index_A, index_B) in corresponding_atoms ]) # atoms in molecule A and B (A molecule numbering) atomset_A_AnotB = set( range(natoms_A) ) - atomset_A_AandB # atoms in molecule A and not B (A molecule numbering) atomset_A_BnotA = set() atomset_B_AandB = set([ index_B for (index_A, index_B) in corresponding_atoms ]) # atoms in molecule B and A (B molecule numbering) atomset_B_BnotA = set( range(natoms_B) ) - atomset_B_AandB # atoms in molecule B and not A (B molecule numbering) atomset_B_AnotB = set() atomset_merged_AandB = set( range(natoms_AandB )) # atoms present in A and B (merged molecule numbering) atomset_merged_AnotB = set( range(natoms_AandB, natoms_AandB + natoms_AnotB) ) # atoms present in A and not B (merged molcule numbering) atomset_merged_BnotA = set( range(natoms_AandB + natoms_AnotB, natoms_AandB + natoms_AnotB + natomsBnotA) ) # atoms present in B and not A (merged molecule numbering) # Construct lists of corresponding atom indices. atom_index = dict() # # Construct merged OpenMM system. # import simtk.unit as units # Select OpenMM API implementation to use. if not mm: import simtk.openmm mm = simtk.openmm # Create new System object. system = mm.System() # Populate merged sytem with atoms. # Masses of atoms in both A and B are geometric mean; otherwise standard mass. # Add particles in A and B. for (index_A, index_B) in corresponding_atoms: # Create particle with geometric mean of masses. mass_A = system_A.getParticleMass(index_A) mass_B = system_B.getParticleMass(index_B) mass = units.sqrt(mass_A * mass_B) system.addParticle(mass) for index_A in atomlist_A_AnotB: mass_A = system_A.getParticleMass(index_A) system.addParticle(mass_A) for index_B in atomlist_B_BnotA: mass_B = system_B.getParticleMass(index_B) system.addParticle(mass_B) # Define helper function. def find_force(system, classname): """ Find the specified Force object in an OpenMM System by classname. ARGUMENTS system (simtk.openmm.System) - system containing forces to be searched classname (string) - classname of Force object to locate RETURNS force (simtk.openmm.Force) - the first Force object encountered with the specified classname, or None if one could not be found """ nforces = system.getNumForces() force = None for index in range(nforces): if isinstance(system.getForce(index), getattr(mm, classname)): force = system.getForce(index) return force # Add bonds. # NOTE: This does not currently deal with bonds that are broken or formed during the transformation. force_A = find_force(system_A, 'HarmonicBondForce') for index in range(force_A.getNumBonds()): # Get bond parameters from molecule A. [iatom_A, jatom_A, length_A, k_A] = force.getBondParameters(index) # Translate atom indices to merged atom indices. (iatom_merged, jatom_merged) = (atom_indices['A'][iatom_A]['merged'], atom_indices['B'][jatom_A]['merged']) # Store bond parameters for random access. bonds[(iatom_merged, jatom_merged)] = (length - A, k_A) force_B = find_force(system_B, 'HarmonicBondForce') for index in range(force_B.getNumBonds()): # Get bond parameters from molecule A. [iatom_B, jatom_B, length_B, k_B] = force.getBondParameters(index) # Translate atom indices to merged atom indices. (iatom_merged, jatom_merged) = (atom_indices['B'][iatom_B]['merged'], atom_indices['B'][jatom_B]['merged']) # Store bond parameters for random access. if (iatom_merged, jatom_merged) in bonds: # Mix bonds. (length_A, k_A) = bonds[(iatom_merged, jatom_merged)] (length, k) = ((1.0 - alchemical_lambda) * length_A + alchemical_lambda * length_B, (1.0 - alchemical_lambda) * k_A + alchemical_lambda * k_B) bonds[(iatom_merged, jatom_merged)] = (length, k) else: bonds[(iatom_merged, jatom_merged)] = (length_B, k_B) # Add bonds to merged topology. force = mm.HarmonicBondForce() for (iatom, jatom) in bonds: # Retrieve bond parameters. (length, j) = bonds[(iatom, jatom)] # Add bond. force.addBond(iatom, jatom, length, k) # Add the Force to the merged topology sytem. system.addForce(force)
def equilibrate_ghmc(system, timestep, collision_rate, temperature, masses, sqrt_kT_over_m, positions, platform, debug=False): nsteps = 500 # number of steps kT = kB * temperature beta = 1.0 / kT # inverse temperature print "Equilibrating for %d GHMC steps (%.3f ps)..." % ( nsteps, (nsteps * timestep) / units.picoseconds) initial_time = time.time() # Assign Maxwell-Boltzmann velocities velocities = sqrt_kT_over_m * numpy.random.standard_normal( size=sqrt_kT_over_m.shape) # Compute Langevin velocity modification factors. gamma = collision_rate * masses sigma2 = (2.0 * kT * gamma) alpha_factor = (1.0 - (timestep / 4.0) * collision_rate) / ( 1.0 + (timestep / 4.0) * collision_rate) x = (timestep / 2.0 * sigma2).in_units_of( units.kilogram**2 / units.mole**2 * units.meter**2 / units.second**2) y = units.Quantity(numpy.sqrt(x / x.unit), units.sqrt(1.0 * x.unit)) beta_factor = (y / (1.0 + (timestep / 4.0) * collision_rate) / masses).in_units_of(velocities.unit) # Create integrator and context. integrator = openmm.VerletIntegrator(timestep) context = openmm.Context(system, integrator, platform) # Compute forces and total energy. context.setPositions(positions) context.setVelocities(velocities) state = context.getState(getForces=True, getEnergy=True) forces = state.getForces(asNumpy=True) kinetic_energy = state.getKineticEnergy() potential_energy = state.getPotentialEnergy() total_energy = kinetic_energy + potential_energy # Create storage for proposed positions and velocities. proposed_positions = copy.deepcopy(positions) proposed_velocities = copy.deepcopy(velocities) naccepted = 0 # number of accepted GHMC steps for step in range(nsteps): # # Velocity modification step. # velocities[:, :] = velocities[:, :] * alpha_factor + units.Quantity( numpy.random.standard_normal(size=positions.shape) * (beta_factor / beta_factor.unit), beta_factor.unit) kinetic_energy = 0.5 * (masses * velocities**2).in_units_of( potential_energy.unit ).sum( ) * potential_energy.unit # have to do this because sum(...) and .sum() don't respect units total_energy = kinetic_energy + potential_energy # # Metropolis-wrapped Velocity Verlet step # proposed_positions[:, :] = positions[:, :] proposed_velocities[:, :] = velocities[:, :] # Half-kick velocities proposed_velocities[:, :] += 0.5 * forces[:, :] / masses[:, :] * timestep # Full-kick positions proposed_positions[:, :] += proposed_velocities[:, :] * timestep # Update force at new positions. context.setVelocities(proposed_velocities) context.setPositions(proposed_positions) state = context.getState(getForces=True, getEnergy=True) proposed_forces = state.getForces(asNumpy=True) proposed_potential_energy = state.getPotentialEnergy() # Half-kick velocities proposed_velocities[:, :] += 0.5 * proposed_forces[:, :] / masses[:, :] * timestep proposed_kinetic_energy = 0.5 * ( masses * proposed_velocities**2 ).in_units_of(potential_energy.unit).sum( ) * potential_energy.unit # have to do this because sum(...) and .sum() don't respect units # Compute new total energy. proposed_total_energy = proposed_kinetic_energy + proposed_potential_energy # Accept or reject, inverting momentum if rejected. du = beta * (proposed_total_energy - total_energy) if (du < 0.0) or (numpy.random.uniform() < numpy.exp(-du)): # Accept and update positions, velocities, forces, and energies. naccepted += 1 positions[:, :] = proposed_positions[:, :] velocities[:, :] = proposed_velocities[:, :] forces[:, :] = proposed_forces[:, :] potential_energy = proposed_potential_energy kinetic_energy = proposed_kinetic_energy else: # Reject, requiring negation of velocities. velocities[:, :] = -velocities[:, :] # # Velocity modification step. # velocities[:, :] = velocities[:, :] * alpha_factor + units.Quantity( numpy.random.standard_normal(size=positions.shape) * (beta_factor / beta_factor.unit), beta_factor.unit) kinetic_energy = 0.5 * (masses * velocities**2).in_units_of( potential_energy.unit ).sum( ) * potential_energy.unit # have to do this because sum(...) and .sum() don't respect units total_energy = kinetic_energy + potential_energy # Print final statistics. fraction_accepted = float(naccepted) / float(nsteps) final_time = time.time() elapsed_time = final_time - initial_time if debug: print "%12.3f s elapsed | accepted %6.3f%%" % ( elapsed_time, fraction_accepted * 100.0) return [positions, velocities, fraction_accepted]
def main(): import doctest import argparse parser = argparse.ArgumentParser(description="Check OpenMM computed energies and forces across all platforms for a suite of test systems.") parser.add_argument('-o', '--outfile', dest='logfile', action='store', type=str, default=None) parser.add_argument('-v', dest='verbose', action='store_true') parser.add_argument('-i', '--input', dest="input_data_path", action="store", type=str) parser.add_argument('-t', '--tuneplatform', dest="tune_pme_platform", action="store", type=str, default=None) parser.add_argument('-p', '--precision', dest="precision", action="store", type=str, default='single') args = parser.parse_args() verbose = args.verbose # Don't display extra debug information. config_root_logger(verbose, log_file_path=args.logfile) # Print version. logger.info("OpenMM version: %s" % openmm.version.version) logger.info("") # List all available platforms logger.info("Available platforms:") for platform_index in range(openmm.Platform.getNumPlatforms()): platform = openmm.Platform.getPlatform(platform_index) logger.info("%5d %s" % (platform_index, platform.getName())) logger.info("") # Test all systems on Reference platform. platform = openmm.Platform.getPlatformByName("Reference") print('Testing Reference platform...') doctest.testmod() # Compute energy error made on all test systems for other platforms. # Make a count of how often set tolerance is exceeded. tests_failed = 0 # number of times tolerance is exceeded tests_passed = 0 # number of times tolerance is not exceeded logger.info("%16s%16s %16s %16s %16s %16s" % ("platform", "precision", "potential", "error", "force mag", "rms error")) reference_platform = openmm.Platform.getPlatformByName("Reference") n_runs=get_num_runs(args.input_data_path) for run in range(n_runs): print("Deserializing XML files for RUN%d" % run) state = XmlSerializer.deserialize(read_file(os.path.join(args.input_data_path,"RUN%d" % run, "state0.xml"))) integrator = XmlSerializer.deserialize(read_file(os.path.join(args.input_data_path,"RUN%d" % run, "integrator.xml"))) system = XmlSerializer.deserialize(read_file(os.path.join(args.input_data_path,"RUN%d" % run, "system.xml"))) # Update system periodic box vectors based on state. system.setDefaultPeriodicBoxVectors(*state.getPeriodicBoxVectors()) # Create test system instance. positions = state.getPositions() # Get PME parameters forces = [ system.getForce(force_index) for force_index in range(system.getNumForces()) ] force_dict = { force.__class__.__name__ : force for force in forces } print("PME parameters:") force = force_dict['NonbondedForce'] print(force.getPMEParameters()) (alpha, nx, ny, nz) = force.getPMEParameters() if alpha == 0.0 / unit.nanometers: # Set PME parameters explicitly. print("Setting PME parameters explicitly...") (alpha, nx, ny, nz) = calc_pme_parameters(system) print (alpha, nx, ny, nz) print(type(nx)) force.setPMEParameters(alpha, int(nx), int(ny), int(nz)) print(force.getPMEParameters()) if args.tune_pme_platform: # Tune PME parameters for specified platform. from optimizepme import optimizePME properties = dict() platform = openmm.Platform.getPlatformByName(args.tune_pme_platform) print("Tuning PME parameters for platform '%s' precision model '%s'..." % (platform.getName(), args.precision)) if (platform.getName() == 'OpenCL'): properties['OpenCLPrecision'] = args.precision elif (platform.getName() == 'CUDA'): properties['CudaPrecision'] = args.precision minCutoff = 0.8 * unit.nanometers maxCutoff = 1.2 * unit.nanometers optimizePME(system, integrator, positions, platform, properties, minCutoff, maxCutoff) class_name = 'RUN%d' % run logger.info("%s (%d atoms)" % (class_name, system.getNumParticles())) # Compute reference potential and force [reference_potential, reference_force] = compute_potential_and_force(system, positions, reference_platform) # Test all platforms. test_success = True for platform_index in range(openmm.Platform.getNumPlatforms()): try: platform = openmm.Platform.getPlatform(platform_index) platform_name = platform.getName() # Define precision models to test. if platform_name == 'Reference': precision_models = ['double'] else: precision_models = ['single'] if platform.supportsDoublePrecision(): precision_models.append('double') for precision_model in precision_models: # Set precision. if platform_name == 'CUDA': platform.setPropertyDefaultValue('CudaPrecision', precision_model) if platform_name == 'OpenCL': platform.setPropertyDefaultValue('OpenCLPrecision', precision_model) # Compute potential and force. [platform_potential, platform_force] = compute_potential_and_force(system, positions, platform) # Compute error in potential. potential_error = platform_potential - reference_potential # Compute per-atom RMS (magnitude) and RMS error in force. force_unit = unit.kilocalories_per_mole / unit.nanometers natoms = system.getNumParticles() force_mse = (((reference_force - platform_force) / force_unit)**2).sum() / natoms * force_unit**2 force_rmse = unit.sqrt(force_mse) force_ms = ((platform_force / force_unit)**2).sum() / natoms * force_unit**2 force_rms = unit.sqrt(force_ms) logger.info("%16s%16s %16.6f kcal/mol %16.6f kcal/mol %16.6f kcal/mol/nm %16.6f kcal/mol/nm" % (platform_name, precision_model, platform_potential / unit.kilocalories_per_mole, potential_error / unit.kilocalories_per_mole, force_rms / force_unit, force_rmse / force_unit)) # Mark whether tolerance is exceeded or not. if abs(potential_error) > ENERGY_TOLERANCE: test_success = False logger.info("%32s WARNING: Potential energy error (%.6f kcal/mol) exceeds tolerance (%.6f kcal/mol). Test failed." % ("", potential_error/unit.kilocalories_per_mole, ENERGY_TOLERANCE/unit.kilocalories_per_mole)) if abs(force_rmse) > FORCE_RMSE_TOLERANCE: test_success = False logger.info("%32s WARNING: Force RMS error (%.6f kcal/mol/nm) exceeds tolerance (%.6f kcal/mol/nm). Test failed." % ("", force_rmse/force_unit, FORCE_RMSE_TOLERANCE/force_unit)) if verbose: for atom_index in range(natoms): for k in range(3): logger.info("%12.6f" % (reference_force[atom_index,k]/force_unit), end="") logger.info(" : ", end="") for k in range(3): logger.info("%12.6f" % (platform_force[atom_index,k]/force_unit), end="") except Exception as e: logger.info(e) if test_success: tests_passed += 1 else: tests_failed += 1 if (test_success is False): # Write XML files of failed tests to aid in debugging. # Place forces into different force groups. forces = [ system.getForce(force_index) for force_index in range(system.getNumForces()) ] force_group_names = dict() group_index = 0 for force_index in range(system.getNumForces()): force_name = forces[force_index].__class__.__name__ if force_name == 'NonbondedForce': forces[force_index].setForceGroup(group_index+1) force_group_names[group_index] = 'NonbondedForce (direct)' group_index += 1 forces[force_index].setReciprocalSpaceForceGroup(group_index+1) force_group_names[group_index] = 'NonbondedForce (reciprocal)' group_index += 1 else: forces[force_index].setForceGroup(group_index+1) force_group_names[group_index] = force_name group_index += 1 ngroups = len(force_group_names) # Test by force group. logger.info("Breakdown of discrepancies by Force component:") nforces = system.getNumForces() for force_group in range(ngroups): force_name = force_group_names[force_group] logger.info(force_name) [reference_potential, reference_force] = compute_potential_and_force_by_force_group(system, positions, reference_platform, force_group) logger.info("%16s%16s %16s %16s %16s %16s" % ("platform", "precision", "potential", "error", "force mag", "rms error")) for platform_index in range(openmm.Platform.getNumPlatforms()): try: platform = openmm.Platform.getPlatform(platform_index) platform_name = platform.getName() # Define precision models to test. if platform_name == 'Reference': precision_models = ['double'] else: precision_models = ['single'] if platform.supportsDoublePrecision(): precision_models.append('double') for precision_model in precision_models: # Set precision. if platform_name == 'CUDA': platform.setPropertyDefaultValue('CudaPrecision', precision_model) if platform_name == 'OpenCL': platform.setPropertyDefaultValue('OpenCLPrecision', precision_model) # Compute potential and force. [platform_potential, platform_force] = compute_potential_and_force_by_force_group(system, positions, platform, force_group) # Compute error in potential. potential_error = platform_potential - reference_potential # Compute per-atom RMS (magnitude) and RMS error in force. force_unit = unit.kilocalories_per_mole / unit.nanometers natoms = system.getNumParticles() force_mse = (((reference_force - platform_force) / force_unit)**2).sum() / natoms * force_unit**2 force_rmse = unit.sqrt(force_mse) force_ms = ((platform_force / force_unit)**2).sum() / natoms * force_unit**2 force_rms = unit.sqrt(force_ms) logger.info("%16s%16s %16.6f kcal/mol %16.6f kcal/mol %16.6f kcal/mol/nm %16.6f kcal/mol/nm" % (platform_name, precision_model, platform_potential / unit.kilocalories_per_mole, potential_error / unit.kilocalories_per_mole, force_rms / force_unit, force_rmse / force_unit)) except Exception as e: logger.info(e) pass logger.info("") logger.info("%d tests failed" % tests_failed) logger.info("%d tests passed" % tests_passed) if (tests_failed > 0): # Signal failure of test. sys.exit(1) else: sys.exit(0)
def G_ana(frameno, pp=False, **kwargs): """ Compute correction free energy for finize-size effects Using the analytical correction scheme Ref Eqn. 14 """ i = int(frameno) MD = pc['MD'] Q_L = pc['Q_L'] Q_P = pc['Q_P'] E_S = pc['eps_s'] E_0 = pc['eps_0'] REL_E = pc['rel_eps'] Ns = pc['Ns'] V = pc['V'] L = pc['L'] Z_ls = pc['Z_ls'] #Cubic lattice-sum Wigner integration constant #Constant of integration pre_fac = 1/(8*np.pi*E_0) chg_diff = ((Q_P + Q_L)**2 - Q_P**2) #Correction for periodicity-induced net-charge interactions #Solvent independent; (+) if QP/QL are same sign #Eqn. 15 NET = -Z_ls * pre_fac * chg_diff * (1/L) * u.AVOGADRO_CONSTANT_NA #Correction for periodicity-induced undersolvation #Solvent dependent; (-) if QP/QL are same sign #Eqn. 16 == Eqn 15 * -(1 - 1/E_S) #USV = Z_ls* pre_fac * chg_diff * (1/L) * u.AVOGADRO_CONSTANT_NA USV = Z_ls* pre_fac * REL_E * chg_diff * (1/L) * u.AVOGADRO_CONSTANT_NA #Get integrated potentials # Input in kcal/mol; Convert to J/mol? # Average potentials are in [UNIT CHARGE]/[ANGS] with open("HET-LIG/pbeq/resu/{}.res".format(i)) as f1: I_L = u.Quantity( np.float(f1.read().split()[2])*u.kilocalories_per_mole) I_L = I_L.in_units_of(u.kilojoules_per_mole) * (u.angstroms**3 / u.elementary_charge) with open("HOM-LIG/pbeq/resu/{}.res".format(i)) as f2: I_0 = u.Quantity( np.float(f2.read().split()[2])*u.kilocalories_per_mole) I_0 = I_0.in_units_of(u.kilojoules_per_mole) * (u.angstroms**3 / u.elementary_charge) with open("HET-PROT/pbeq/resu/{}.res".format(i)) as f3: I_P = u.Quantity( np.float(f3.read().split()[2])*u.kilocalories_per_mole) I_P = I_P.in_units_of(u.kilojoules_per_mole) * (u.angstroms**3 / u.elementary_charge) #Correction for residual integrated potential effects #Eqn. 17 RIP = ((I_P + I_L)*(Q_P + Q_L) - I_P * Q_P) * 1/V #Solvation contribution to the residual integrated potential of ligand #Eqn 27 IL_slv = 0 IL_slv = I_L - I_0 #Calculate effective radius of solvation for ligand #Eqn 26 sol_v = (pre_fac * (4*np.pi/3) * REL_E * Q_L)**-1 rad = SI(sol_v * IL_slv / u.AVOGADRO_CONSTANT_NA) if rad._value < 0: print("!!!WARNING: Eff. solvation radius for ligand is negative.\n Setting R_L-{}=0".format(i)) rad = u.Quantity(0 * u.angstroms**2) R_L = u.sqrt(rad) #Emperical correction #Eqn 25. emp_c = -pre_fac * (16*np.pi**2)/45 * REL_E EMP = emp_c * chg_diff * (R_L**5 / V**2) * u.AVOGADRO_CONSTANT_NA #Correction for discrete solvent effects #Eqn. 35 gamma_s = quad_moment() density = Ns / V DSC = (-gamma_s * Q_L) / (6 * E_0) * density * u.AVOGADRO_CONSTANT_NA #Analytical correction #Eqn 14 ANA = NET + USV + RIP + EMP COR = ANA + DSC NPBC = MD + COR if pp == True: print(pc) print("I_P = {}".format(I_P)) print("I_L = {}".format(I_L)) print("I_0 = {}".format(I_0 )) print("R_L = {}".format(R_L)) print("NET = {}".format( KJ(NET)) ) print("USV = {}".format( KJ(USV)) ) print("RIP = {}".format( KJ(RIP)) ) print("EMP = {}".format( KJ(EMP)) ) print("DSC = {}".format( KJ(DSC)) ) print("ANA = {}".format( KJ(ANA)) ) print("COR = {}".format( KJ(COR)) ) return KCAL(ANA), KCAL(DSC), KCAL(COR), KCAL(NPBC)
def _addAtomsToTopology(self, heavyAtomsOnly, omitUnknownMolecules): """Create a new Topology in which missing atoms have been added.""" newTopology = app.Topology() newPositions = []*unit.nanometer newAtoms = [] existingAtomMap = {} addedAtomMap = {} addedOXT = [] for chain in self.topology.chains(): if omitUnknownMolecules and not any(residue.name in self.templates for residue in chain.residues()): continue chainResidues = list(chain.residues()) newChain = newTopology.addChain() for indexInChain, residue in enumerate(chain.residues()): # Insert missing residues here. if (chain.index, indexInChain) in self.missingResidues: insertHere = self.missingResidues[(chain.index, indexInChain)] endPosition = self._computeResidueCenter(residue) if indexInChain > 0: startPosition = self._computeResidueCenter(chainResidues[indexInChain-1]) else: outward = endPosition-self.centroid norm = unit.norm(outward) if norm > 0*unit.nanometer: outward *= len(insertHere)*0.5*unit.nanometer/norm startPosition = endPosition+outward self._addMissingResiduesToChain(newChain, insertHere, startPosition, endPosition, residue, newAtoms, newPositions) # Create the new residue and add existing heavy atoms. newResidue = newTopology.addResidue(residue.name, newChain) addResiduesAfter = (residue == chainResidues[-1] and (chain.index, indexInChain+1) in self.missingResidues) for atom in residue.atoms(): if not heavyAtomsOnly or (atom.element is not None and atom.element != hydrogen): if atom.name == 'OXT' and (chain.index, indexInChain+1) in self.missingResidues: continue # Remove terminal oxygen, since we'll add more residues after this one newAtom = newTopology.addAtom(atom.name, atom.element, newResidue) existingAtomMap[atom] = newAtom newPositions.append(self.positions[atom.index]) if residue in self.missingAtoms: # Find corresponding atoms in the residue and the template. template = self.templates[residue.name] atomPositions = dict((atom.name, self.positions[atom.index]) for atom in residue.atoms()) points1 = [] points2 = [] for atom in template.topology.atoms(): if atom.name in atomPositions: points1.append(atomPositions[atom.name].value_in_unit(unit.nanometer)) points2.append(template.positions[atom.index].value_in_unit(unit.nanometer)) # Compute the optimal transform to overlay them. (translate2, rotate, translate1) = _overlayPoints(points1, points2) # Add the missing atoms. addedAtomMap[residue] = {} for atom in self.missingAtoms[residue]: newAtom = newTopology.addAtom(atom.name, atom.element, newResidue) newAtoms.append(newAtom) addedAtomMap[residue][atom] = newAtom templatePosition = template.positions[atom.index].value_in_unit(unit.nanometer) newPositions.append((mm.Vec3(*np.dot(rotate, templatePosition+translate2))+translate1)*unit.nanometer) if residue in self.missingTerminals: terminalsToAdd = self.missingTerminals[residue] else: terminalsToAdd = None # If this is the end of the chain, add any missing residues that come after it. if residue == chainResidues[-1] and (chain.index, indexInChain+1) in self.missingResidues: insertHere = self.missingResidues[(chain.index, indexInChain+1)] if len(insertHere) > 0: startPosition = self._computeResidueCenter(residue) outward = startPosition-self.centroid norm = unit.norm(outward) if norm > 0*unit.nanometer: outward *= len(insertHere)*0.5*unit.nanometer/norm endPosition = startPosition+outward self._addMissingResiduesToChain(newChain, insertHere, startPosition, endPosition, residue, newAtoms, newPositions) newResidue = list(newChain.residues())[-1] if newResidue.name in proteinResidues: terminalsToAdd = ['OXT'] else: terminalsToAdd = None # If a terminal OXT is missing, add it. if terminalsToAdd is not None: atomPositions = dict((atom.name, newPositions[atom.index].value_in_unit(unit.nanometer)) for atom in newResidue.atoms()) if 'OXT' in terminalsToAdd: newAtom = newTopology.addAtom('OXT', oxygen, newResidue) newAtoms.append(newAtom) addedOXT.append(newAtom) d_ca_o = atomPositions['O']-atomPositions['CA'] d_ca_c = atomPositions['C']-atomPositions['CA'] d_ca_c /= unit.sqrt(unit.dot(d_ca_c, d_ca_c)) v = d_ca_o - d_ca_c*unit.dot(d_ca_c, d_ca_o) newPositions.append((atomPositions['O']+2*v)*unit.nanometer) newTopology.setUnitCellDimensions(self.topology.getUnitCellDimensions()) newTopology.createStandardBonds() newTopology.createDisulfideBonds(newPositions) # Return the results. return (newTopology, newPositions, newAtoms, existingAtomMap)
sigma = 3.4 * unit.angstrom epsilon = 0.238 * unit.kilocalories_per_mole #reduced_density = 0.860 # reduced_density = density * (sigma**3) reduced_density = 0.960 # reduced_density = density * (sigma**3) reduced_temperature = 0.850 # reduced_temperature = kB * temperature / epsilon reduced_pressure = 1.2660 # reduced_pressure = pressure * (sigma**3) / epsilon platform_name = 'CUDA' # OpenMM platform name to use for simulation platform = openmm.Platform.getPlatformByName(platform_name) data_directory = 'data' # Directory in which data is to be stored r0 = 2.0**( 1. / 6.) * sigma # minimum potential distance for Lennard-Jones interaction characteristic_timescale = unit.sqrt( (mass * r0**2) / (72 * epsilon)) # characteristic timescale for bound Lennard-Jones interaction # http://borisv.lk.net/matsc597c-1997/simulations/Lecture5/node3.html timestep = 0.01 * characteristic_timescale # integrator timestep # From http://www.cstl.nist.gov/srs/LJ_PURE/md.htm #characteristic_timescale = unit.sqrt(mass * sigma**2 / epsilon) #timestep = 0.05 * characteristic_timescale print "characteristic timescale = %.3f ps" % (characteristic_timescale / unit.picoseconds) print "timestep = %.12f ps" % (timestep / unit.picoseconds) collision_rate = 5.0 / unit.picoseconds # collision rate for Langevin thermostat barostat_frequency = 25 # number of steps between barostat updates
output_dict[system_name].update(system_data) system_data = output_dict[system_name] # Shortcut. # If this data has two values, combine: First deal with measured values # Note that Kd/Ka values should be combined as free energies (since that's the normally distributed quantity) for data_type in ['Kd', 'Ka', 'DH']: if data_type + '_1' in system_data: if 'DH' in data_type: #just take mean final_val = np.mean([ system_data[data_type + '_1'], system_data[data_type + '_2'] ]) system_data[data_type] = final_val # Also compute uncertainty -- the larger of the propagated uncertainty and the standard error in the mean final_unc = u.sqrt(system_data['d' + data_type + '_1']**2 + system_data['d' + data_type + '_2']**2) std_err = u.sqrt( 0.5 * ((system_data[data_type + '_1'] - final_val)**2 + (system_data[data_type + '_2'] - final_val)**2)) if std_err > final_unc: final_unc = std_err system_data['d' + data_type] = final_unc #Otherwise first convert to free energy then take mean elif 'Kd' in data_type: #First convert to free energy then take mean # If we have Kd data instead of Ka data, convert if 'Kd_1' in system_data and not 'Ka_1' in system_data: system_data['Ka_1'] = 1. / system_data['Kd_1'] # Handle uncertainty -- 1/Kd^2 * dKd system_data['dKa_1'] = system_data['dKd_1'] / ( system_data['Kd_1']**2) system_data['Ka_2'] = 1. / system_data['Kd_2']
nsteps = 250 # number of steps per interation deviceid = 0 print 'nsteps: ', nsteps # Create system. [system, coordinates] = wcadimer.WCADimer() # Form vectors of masses and sqrt(kT/m) for force propagation and velocity randomization. print "Creating masses array..." nparticles = system.getNumParticles() masses = units.Quantity(numpy.zeros([nparticles,3], numpy.float64), units.amu) for particle_index in range(nparticles): masses[particle_index,:] = system.getParticleMass(particle_index) sqrt_kT_over_m = units.Quantity(numpy.zeros([nparticles,3], numpy.float64), units.nanometers / units.picosecond) for particle_index in range(nparticles): sqrt_kT_over_m[particle_index,:] = units.sqrt(kT / masses[particle_index,0]) # standard deviation of velocity distribution for each coordinate for this atom # List all available platforms print "Available platforms:" for platform_index in range(openmm.Platform.getNumPlatforms()): platform = openmm.Platform.getPlatform(platform_index) print "%5d %s" % (platform_index, platform.getName()) print "" # Select platform. #platform = openmm.Platform.getPlatformByName("CPU") platform = openmm.Platform.getPlatformByName("CUDA") min_platform = openmm.Platform.getPlatformByName("Reference") for prop in platform.getPropertyNames(): print prop, platform.getPropertyDefaultValue(prop)
def build_custom_system(reference_system, receptor_atoms, ligand_atoms, valence_lambda, coulomb_lambda, vdw_lambda, annihilate=False): """ Build alchemically-modified system where ligand is decoupled or annihilated using Custom*Force classes. """ # Create new system. system = openmm.System() # Set periodic box vectors. [a,b,c] = reference_system.getDefaultPeriodicBoxVectors() system.setDefaultPeriodicBoxVectors(a,b,c) # Add atoms. for atom_index in range(reference_system.getNumParticles()): mass = reference_system.getParticleMass(atom_index) system.addParticle(mass) # Add constraints for constraint_index in range(reference_system.getNumConstraints()): [iatom, jatom, r0] = reference_system.getConstraintParameters(constraint_index) system.addConstraint(iatom, jatom, r0) # Perturb force terms. for force_index in range(reference_system.getNumForces()): reference_force = reference_system.getForce(force_index) # Dispatch forces if isinstance(reference_force, openmm.HarmonicBondForce): # HarmonicBondForce force = openmm.HarmonicBondForce() for bond_index in range(reference_force.getNumBonds()): # Retrieve parameters. [iatom, jatom, r0, K] = reference_force.getBondParameters(bond_index) # Annihilate if directed. if annihilate and (iatom in ligand_atoms) and (jatom in ligand_atoms): K *= valence_lambda # Add bond parameters. force.addBond(iatom, jatom, r0, K) # Add force to new system. system.addForce(force) elif isinstance(reference_force, openmm.HarmonicAngleForce): # HarmonicAngleForce force = openmm.HarmonicAngleForce() for angle_index in range(reference_force.getNumAngles()): # Retrieve parameters. [iatom, jatom, katom, theta0, Ktheta] = reference_force.getAngleParameters(angle_index) # Annihilate if directed: if annihilate and (iatom in ligand_atoms) and (jatom in ligand_atoms) and (katom in ligand_atoms): Ktheta *= valence_lambda # Add parameters. force.addAngle(iatom, jatom, katom, theta0, Ktheta) # Add force to system. system.addForce(force) elif isinstance(reference_force, openmm.PeriodicTorsionForce): # PeriodicTorsionForce force = openmm.PeriodicTorsionForce() for torsion_index in range(reference_force.getNumTorsions()): # Retrieve parmaeters. [particle1, particle2, particle3, particle4, periodicity, phase, k] = reference_force.getTorsionParameters(torsion_index) # Annihilate if directed: if annihilate and (particle1 in ligand_atoms) and (particle2 in ligand_atoms) and (particle3 in ligand_atoms) and (particle4 in ligand_atoms): k *= valence_lambda # Add parameters. force.addTorsion(particle1, particle2, particle3, particle4, periodicity, phase, k) # Add force to system. system.addForce(force) elif isinstance(reference_force, openmm.NonbondedForce): # NonbondedForce will handle charges and exception interactions. force = openmm.NonbondedForce() for particle_index in range(reference_force.getNumParticles()): # Retrieve parameters. [charge, sigma, epsilon] = reference_force.getParticleParameters(particle_index) # Remove Lennard-Jones interactions, which will be handled by CustomNonbondedForce. epsilon *= 0.0 # Alchemically modify charges. if particle_index in ligand_atoms: charge *= coulomb_lambda # Add modified particle parameters. force.addParticle(charge, sigma, epsilon) for exception_index in range(reference_force.getNumExceptions()): # Retrieve parameters. [iatom, jatom, chargeprod, sigma, epsilon] = reference_force.getExceptionParameters(exception_index) # Alchemically modify epsilon and chargeprod. # Note that exceptions are handled by NonbondedForce and not CustomNonbondedForce. if annihilate and (iatom in ligand_atoms) and (jatom in ligand_atoms): epsilon *= vdw_lambda chargeprod *= coulomb_lambda # Add modified exception parameters. force.addException(iatom, jatom, chargeprod, sigma, epsilon) if not annihilate: print ligand_atoms # Build a list of exceptions. exceptions = set() for exception_index in range(reference_force.getNumExceptions()): [iatom, jatom, chargeprod, sigma, epsilon] = reference_force.getExceptionParameters(exception_index) exceptions.add( (iatom,jatom) ) exceptions.add( (jatom,iatom) ) # Add new intraligand interactions. for iatom in ligand_atoms: for jatom in ligand_atoms: if (iatom,jatom) not in exceptions: exceptions.add( (iatom,jatom) ) exceptions.add( (jatom,iatom) ) [charge1, sigma1, epsilon1] = reference_force.getParticleParameters(iatom) [charge2, sigma2, epsilon2] = reference_force.getParticleParameters(jatom) chargeprod = charge1*charge2 sigma = 0.5 * (sigma1 + sigma2) epsilon = units.sqrt(epsilon1*epsilon2) print "chargeprod = %s, sigma = %s, epsilon = %s" % (str(chargeprod), str(sigma), str(epsilon)) force.addException(iatom, jatom, chargeprod, sigma, epsilon) # Set parameters. force.setNonbondedMethod( reference_force.getNonbondedMethod() ) force.setCutoffDistance( reference_force.getCutoffDistance() ) force.setReactionFieldDielectric( reference_force.getReactionFieldDielectric() ) force.setEwaldErrorTolerance( reference_force.getEwaldErrorTolerance() ) # Add force to new system. system.addForce(force) # CustomNonbondedForce # Softcore potential. energy_expression = "4*epsilon*lambda*x*(x-1.0);" energy_expression += "x = 1.0/(alpha*(1.0-lambda) + (r/sigma)^6);" energy_expression += "epsilon = sqrt(epsilon1*epsilon2);" energy_expression += "sigma = 0.5*(sigma1 + sigma2);" energy_expression += "lambda = lambda1*lambda2;" force = openmm.CustomNonbondedForce(energy_expression) alpha = 0.5 # softcore parameter force.addGlobalParameter("alpha", alpha); force.addPerParticleParameter("sigma") force.addPerParticleParameter("epsilon") force.addPerParticleParameter("lambda"); for particle_index in range(reference_force.getNumParticles()): # Retrieve parameters. [charge, sigma, epsilon] = reference_force.getParticleParameters(particle_index) # Alchemically modify parameters. if particle_index in ligand_atoms: force.addParticle([sigma, epsilon, vdw_lambda]) else: force.addParticle([sigma, epsilon, 1.0]) for exception_index in range(reference_force.getNumExceptions()): # Retrieve parameters. [iatom, jatom, chargeprod, sigma, epsilon] = reference_force.getExceptionParameters(exception_index) # All exceptions are handled by NonbondedForce, so we exclude all these here. force.addExclusion(iatom, jatom) force.setNonbondedMethod( reference_force.getNonbondedMethod() ) force.setCutoffDistance( reference_force.getCutoffDistance() ) system.addForce(force) elif isinstance(reference_force, openmm.GBSAOBCForce): # GBSAOBCForce # Annihilate ligand interactions. solvent_dielectric = reference_force.getSolventDielectric() solute_dielectric = reference_force.getSoluteDielectric() force = createCustomSoftcoreGBOBC(solvent_dielectric, solute_dielectric, igb=5) for particle_index in range(reference_force.getNumParticles()): # Retrieve parameters. [charge, radius, scaling_factor] = reference_force.getParticleParameters(particle_index) # Alchemically modify parameters. if particle_index in ligand_atoms: # Scale charge and contribution to GB integrals. force.addParticle([charge*coulomb_lambda, radius, scaling_factor, coulomb_lambda]) else: # Don't modulate GB. force.addParticle([charge, radius, scaling_factor, 1.0]) # Add force to new system. system.addForce(force) if not annihilate: # Add ligand solvated component back if decoupling. solvent_dielectric = reference_force.getSolventDielectric() solute_dielectric = reference_force.getSoluteDielectric() force = createCustomSoftcoreGBOBC(solvent_dielectric, solute_dielectric, igb=5) for particle_index in range(reference_force.getNumParticles()): # Retrieve parameters. [charge, radius, scaling_factor] = reference_force.getParticleParameters(particle_index) # Alchemically modify parameters. if particle_index in ligand_atoms: # Scale charge and contribution to GB integrals. force.addParticle([charge*(1-coulomb_lambda), radius, scaling_factor, 1.0 - coulomb_lambda]) else: # Turn off receptor contributions. force.addParticle([charge, radius, scaling_factor, 0.0]) # Add force to new system. system.addForce(force) else: # Don't add unrecognized forces. pass return system
# naccept = integrator.getGlobalVariable(global_variables['naccept']) # ntrials = integrator.getGlobalVariable(global_variables['ntrials']) # print "accepted %d / %d (%.3f %%)" % (naccept, ntrials, float(naccept)/float(ntrials)*100.0) # Accumulate statistics. x_n[iteration] = state.getPositions(asNumpy=True)[0, 0] / units.angstroms potential_n[iteration] = final_potential_energy / kT kinetic_n[iteration] = final_kinetic_energy / kT temperature_n[iteration] = instantaneous_temperature / units.kelvin delta_n[iteration] = delta_total_energy / kT # Compute expected statistics for harmonic oscillator. K = 100.0 * units.kilocalories_per_mole / units.angstroms**2 beta = 1.0 / kT x_mean_exact = 0.0 # mean, in angstroms x_std_exact = 1.0 / units.sqrt( beta * K) / units.angstroms # std dev, in angstroms # Analyze statistics. g = statisticalInefficiency(potential_n) Neff = niterations / g # number of effective samples x_mean = x_n.mean() dx_mean = x_n.std() / numpy.sqrt(Neff) x_mean_error = x_mean - x_mean_exact x_var = x_n.var() dx_var = x_var * numpy.sqrt(2. / (Neff - 1)) x_std = x_n.std() dx_std = 0.5 * dx_var / x_std x_std_error = x_std - x_std_exact
kB = units.BOLTZMANN_CONSTANT_kB * units.AVOGADRO_CONSTANT_NA #============================================================================================= # DEFAULT PARAMETERS #============================================================================================= natoms = 216 # number of particles # WCA fluid parameters (argon). mass = 39.9 * units.amu # reference mass sigma = 3.4 * units.angstrom # reference lengthscale epsilon = 120.0 * units.kelvin * kB # reference energy r_WCA = 2.**(1./6.) * sigma # interaction truncation range tau = units.sqrt(sigma**2 * mass / epsilon) # characteristic timescale # Simulation conditions. temperature = 0.824 / (kB / epsilon) # temperature kT = kB * temperature # thermal energy beta = 1.0 / kT # inverse temperature density = 0.96 / sigma**3 # default density stable_timestep = 0.001 * tau # stable timestep collision_rate = 1 / tau # collision rate for Langevin interator # Dimer potential parameters. h = 5.0 * kT # barrier height r0 = r_WCA # compact state distance w = 0.5 * r_WCA # extended state distance is r0 + 2*w #=============================================================================================
# Add driven collective variables (dihedral angles): atoms = [(a.name, a.residue.name) for a in topology.atoms()] psi = openmm.CustomTorsionForce('theta') psi.addTorsion(*[atoms.index(i) for i in psi_atoms], []) psi = afed.DrivenCollectiveVariable('psi', psi, unit.radians, period=360*unit.degrees) phi = openmm.CustomTorsionForce('theta') phi.addTorsion(*[atoms.index(i) for i in phi_atoms], []) phi = afed.DrivenCollectiveVariable('phi', phi, unit.radians, period=360*unit.degrees) # Add driver parameters [Ref: Chen et al., JCP 137 (2), art. 024102, 2012]: T_dihedrals = 1500*unit.kelvin mass_dihedrals = 168*unit.dalton*(unit.angstroms/unit.radian)**2 K_dihedrals = 2780*unit.kilocalories_per_mole/unit.radians**2 velocity_scale = unit.sqrt(unit.BOLTZMANN_CONSTANT_kB*unit.AVOGADRO_CONSTANT_NA*T_dihedrals/mass_dihedrals) psi_driver = afed.DriverParameter('psi_s', unit.radians, psi.evaluate(positions), T_dihedrals, velocity_scale, -180*unit.degrees, 180*unit.degrees, periodic=True) phi_driver = afed.DriverParameter('phi_s', unit.radians, phi.evaluate(positions), T_dihedrals, velocity_scale, -180*unit.degrees, 180*unit.degrees, periodic=True) # Add driving force: dihedrals = afed.HarmonicDrivingForce() dihedrals.addPair(psi, psi_driver, K_dihedrals) dihedrals.addPair(phi, phi_driver, K_dihedrals) dihedrals.setForceGroup(0) # parameter velocity integration at fastest time scale system.addForce(dihedrals) # Define AFED integrator: integrator = afed.MassiveMiddleNHCIntegrator(
def main(): import doctest import argparse parser = argparse.ArgumentParser(description="Check OpenMM computed energies and forces across all platforms for a suite of test systems.") parser.add_argument('-o', '--outfile', dest='logfile', action='store', type=str, default=None) parser.add_argument('-v', dest='verbose', action='store_true') args = parser.parse_args() verbose = args.verbose # Don't display extra debug information. config_root_logger(verbose, log_file_path=args.logfile) # Print version. logger.info("OpenMM version: %s" % openmm.version.version) logger.info("") # List all available platforms logger.info("Available platforms:") for platform_index in range(openmm.Platform.getNumPlatforms()): platform = openmm.Platform.getPlatform(platform_index) logger.info("%5d %s" % (platform_index, platform.getName())) logger.info("") # Test all systems on Reference platform. platform = openmm.Platform.getPlatformByName("Reference") print('Testing Reference platform...') doctest.testmod() # Compute energy error made on all test systems for other platforms. # Make a count of how often set tolerance is exceeded. tests_failed = 0 # number of times tolerance is exceeded tests_passed = 0 # number of times tolerance is not exceeded logger.info("%16s%16s %16s %16s %16s %16s" % ("platform", "precision", "potential", "error", "force mag", "rms error")) reference_platform = openmm.Platform.getPlatformByName("Reference") testsystem_classes = get_all_subclasses(testsystems.TestSystem) for testsystem_class in testsystem_classes: class_name = testsystem_class.__name__ try: testsystem = testsystem_class() except ImportError as e: logger.info(e) logger.info("Skipping %s due to missing dependency" % class_name) continue # Create test system instance. testsystem = testsystem_class() [system, positions] = [testsystem.system, testsystem.positions] logger.info("%s (%d atoms)" % (class_name, testsystem.system.getNumParticles())) # Compute reference potential and force [reference_potential, reference_force] = compute_potential_and_force(system, positions, reference_platform) # Test all platforms. test_success = True for platform_index in range(openmm.Platform.getNumPlatforms()): try: platform = openmm.Platform.getPlatform(platform_index) platform_name = platform.getName() # Define precision models to test. if platform_name == 'Reference': precision_models = ['double'] else: precision_models = ['single'] if platform.supportsDoublePrecision(): precision_models.append('double') for precision_model in precision_models: # Set precision. if platform_name == 'CUDA': platform.setPropertyDefaultValue('CudaPrecision', precision_model) if platform_name == 'OpenCL': platform.setPropertyDefaultValue('OpenCLPrecision', precision_model) # Compute potential and force. [platform_potential, platform_force] = compute_potential_and_force(system, positions, platform) # Compute error in potential. potential_error = platform_potential - reference_potential # Compute per-atom RMS (magnitude) and RMS error in force. force_unit = units.kilocalories_per_mole / units.nanometers natoms = system.getNumParticles() force_mse = (((reference_force - platform_force) / force_unit)**2).sum() / natoms * force_unit**2 force_rmse = units.sqrt(force_mse) force_ms = ((platform_force / force_unit)**2).sum() / natoms * force_unit**2 force_rms = units.sqrt(force_ms) logger.info("%16s%16s %16.6f kcal/mol %16.6f kcal/mol %16.6f kcal/mol/nm %16.6f kcal/mol/nm" % (platform_name, precision_model, platform_potential / units.kilocalories_per_mole, potential_error / units.kilocalories_per_mole, force_rms / force_unit, force_rmse / force_unit)) # Mark whether tolerance is exceeded or not. if abs(potential_error) > ENERGY_TOLERANCE: test_success = False logger.info("%32s WARNING: Potential energy error (%.6f kcal/mol) exceeds tolerance (%.6f kcal/mol). Test failed." % ("", potential_error/units.kilocalories_per_mole, ENERGY_TOLERANCE/units.kilocalories_per_mole)) if abs(force_rmse) > FORCE_RMSE_TOLERANCE: test_success = False logger.info("%32s WARNING: Force RMS error (%.6f kcal/mol/nm) exceeds tolerance (%.6f kcal/mol/nm). Test failed." % ("", force_rmse/force_unit, FORCE_RMSE_TOLERANCE/force_unit)) if verbose: for atom_index in range(natoms): for k in range(3): logger.info("%12.6f" % (reference_force[atom_index,k]/force_unit), end="") logger.info(" : ", end="") for k in range(3): logger.info("%12.6f" % (platform_force[atom_index,k]/force_unit), end="") except Exception as e: logger.info(e) if test_success: tests_passed += 1 else: tests_failed += 1 if (test_success is False): # Write XML files of failed tests to aid in debugging. logger.info("Writing failed test system to '%s'.{system,state}.xml ..." % testsystem.name) [system_xml, state_xml] = testsystem.serialize() xml_file = open(testsystem.name + '.system.xml', 'w') xml_file.write(system_xml) xml_file.close() xml_file = open(testsystem.name + '.state.xml', 'w') xml_file.write(state_xml) xml_file.close() # Place forces into different force groups. forces = [ system.getForce(force_index) for force_index in range(system.getNumForces()) ] force_group_names = dict() group_index = 0 for force_index in range(system.getNumForces()): force_name = forces[force_index].__class__.__name__ if force_name == 'NonbondedForce': forces[force_index].setForceGroup(group_index+1) force_group_names[group_index] = 'NonbondedForce (direct)' group_index += 1 forces[force_index].setReciprocalSpaceForceGroup(group_index+1) force_group_names[group_index] = 'NonbondedForce (reciprocal)' group_index += 1 else: forces[force_index].setForceGroup(group_index+1) force_group_names[group_index] = force_name group_index += 1 ngroups = len(force_group_names) # Test by force group. logger.info("Breakdown of discrepancies by Force component:") nforces = system.getNumForces() for force_group in range(ngroups): force_name = force_group_names[force_group] logger.info(force_name) [reference_potential, reference_force] = compute_potential_and_force_by_force_group(system, positions, reference_platform, force_group) logger.info("%16s%16s %16s %16s %16s %16s" % ("platform", "precision", "potential", "error", "force mag", "rms error")) for platform_index in range(openmm.Platform.getNumPlatforms()): try: platform = openmm.Platform.getPlatform(platform_index) platform_name = platform.getName() # Define precision models to test. if platform_name == 'Reference': precision_models = ['double'] else: precision_models = ['single'] if platform.supportsDoublePrecision(): precision_models.append('double') for precision_model in precision_models: # Set precision. if platform_name == 'CUDA': platform.setPropertyDefaultValue('CudaPrecision', precision_model) if platform_name == 'OpenCL': platform.setPropertyDefaultValue('OpenCLPrecision', precision_model) # Compute potential and force. [platform_potential, platform_force] = compute_potential_and_force_by_force_group(system, positions, platform, force_group) # Compute error in potential. potential_error = platform_potential - reference_potential # Compute per-atom RMS (magnitude) and RMS error in force. force_unit = units.kilocalories_per_mole / units.nanometers natoms = system.getNumParticles() force_mse = (((reference_force - platform_force) / force_unit)**2).sum() / natoms * force_unit**2 force_rmse = units.sqrt(force_mse) force_ms = ((platform_force / force_unit)**2).sum() / natoms * force_unit**2 force_rms = units.sqrt(force_ms) logger.info("%16s%16s %16.6f kcal/mol %16.6f kcal/mol %16.6f kcal/mol/nm %16.6f kcal/mol/nm" % (platform_name, precision_model, platform_potential / units.kilocalories_per_mole, potential_error / units.kilocalories_per_mole, force_rms / force_unit, force_rmse / force_unit)) except Exception as e: logger.info(e) pass logger.info("") logger.info("%d tests failed" % tests_failed) logger.info("%d tests passed" % tests_passed) if (tests_failed > 0): # Signal failure of test. sys.exit(1) else: sys.exit(0)
def createUnfoldedSurrogate2(topology, reference_system, locality=5): # Create system deep copy. system = copy.deepcopy(reference_system) # Modify forces as appropriate, copying other forces without modification. forces_to_remove = list() nforces = reference_system.getNumForces() for force_index in range(nforces): reference_force = reference_system.getForce(force_index) force_name = reference_force.__class__.__name__ print force_name if force_name == 'NonbondedForce': forces_to_remove.append(force_index) # Create CustomBondForce instead. energy_expression = "E_LJ + E_Coulomb + E_GB;" energy_expression += "E_LJ = 4*epsilon*((sigma/r)^12 - (sigma/r)^6);" energy_expression += "E_Coulomb = 138.935456*chargeprod/r;" energy_expression += "E_GB = -0.5*(1/epsilon_solute - 1/epsilon_solvent)*chargeprod/f_GB;" energy_expression += "f_GB = (r^2 + RiRj*exp(-r/(4*RiRj)))^0.5;" energy_expression += "RiRj = 0.25;" custom_bond_force = openmm.CustomBondForce(energy_expression) custom_bond_force.addPerBondParameter( "chargeprod") # charge product custom_bond_force.addPerBondParameter( "sigma") # Lennard-Jones sigma custom_bond_force.addPerBondParameter( "epsilon") # Lennard-Jones epsilon custom_bond_force.addGlobalParameter("epsilon_solvent", 78.5) custom_bond_force.addGlobalParameter("epsilon_solute", 1) system.addForce(custom_bond_force) # Add exclusions. print("Building exclusions...") from sets import Set exceptions = Set() for index in range(reference_force.getNumExceptions()): [atom1_index, atom2_index, chargeprod, sigma, epsilon] = reference_force.getExceptionParameters(index) custom_bond_force.addBond(atom1_index, atom2_index, [chargeprod, sigma, epsilon]) if atom2_index < atom1_index: exceptions.add((atom2_index, atom1_index)) else: exceptions.add((atom1_index, atom2_index)) # Add local interactions. print("Adding local interactions...") for atom1 in topology.atoms(): [charge1, sigma1, epsilon1] = reference_force.getParticleParameters(atom1.index) for atom2 in topology.atoms(): if (atom1.index < atom2.index) and ( abs(atom1.residue.index - atom2.residue.index) <= locality) and ((atom1.index, atom2.index) not in exceptions): [charge2, sigma2, epsilon2 ] = reference_force.getParticleParameters(atom1.index) chargeprod = charge1 * charge2 sigma = 0.5 * (sigma1 + sigma2) epsilon = unit.sqrt(epsilon1 * epsilon2) custom_bond_force.addBond(atom1.index, atom2.index, [chargeprod, sigma, epsilon]) print("%d custom bond terms added." % custom_bond_force.getNumBonds()) elif force_name == 'GBSAOBCForce': forces_to_remove.append(force_index) # Remove forces scheduled for removal. print("Removing forces:") print(forces_to_remove) for force_index in forces_to_remove[::-1]: system.removeForce(force_index) return system
def computeHarmonicOscillatorExpectations(K, mass, temperature): """ Compute mean and variance of potential and kinetic energies for a 3D harmonic oscillator. NOTES Numerical quadrature is used to compute the mean and standard deviation of the potential energy. Mean and standard deviation of the kinetic energy, as well as the absolute free energy, is computed analytically. ARGUMENTS K (simtk.unit.Quantity) - spring constant mass (simtk.unit.Quantity) - mass of particle temperature (simtk.unit.Quantity) - temperature RETURNS values (dict) TODO Replace this with built-in analytical expectations for new repex.testsystems classes. """ values = dict() # Compute thermal energy and inverse temperature from specified temperature. kB = units.BOLTZMANN_CONSTANT_kB * units.AVOGADRO_CONSTANT_NA kT = kB * temperature # thermal energy beta = 1.0 / kT # inverse temperature # Compute standard deviation along one dimension. sigma = 1.0 / units.sqrt(beta * K) # Define limits of integration along r. r_min = 0.0 * units.nanometers # initial value for integration r_max = 10.0 * sigma # maximum radius to integrate to # Compute mean and std dev of potential energy. V = lambda r : (K/2.0) * (r*units.nanometers)**2 / units.kilojoules_per_mole # potential in kJ/mol, where r in nm q = lambda r : 4.0 * math.pi * r**2 * math.exp(-beta * (K/2.0) * (r*units.nanometers)**2) # q(r), where r in nm (IqV2, dIqV2) = scipy.integrate.quad(lambda r : q(r) * V(r)**2, r_min / units.nanometers, r_max / units.nanometers) (IqV, dIqV) = scipy.integrate.quad(lambda r : q(r) * V(r), r_min / units.nanometers, r_max / units.nanometers) (Iq, dIq) = scipy.integrate.quad(lambda r : q(r), r_min / units.nanometers, r_max / units.nanometers) values['potential'] = dict() values['potential']['mean'] = (IqV / Iq) * units.kilojoules_per_mole values['potential']['stddev'] = (IqV2 / Iq) * units.kilojoules_per_mole # Compute mean and std dev of kinetic energy. values['kinetic'] = dict() values['kinetic']['mean'] = (3./2.) * kT values['kinetic']['stddev'] = math.sqrt(3./2.) * kT # Compute dimensionless free energy. # f = - \ln \int_{-\infty}^{+\infty} \exp[-\beta K x^2 / 2] # = - \ln \int_{-\infty}^{+\infty} \exp[-x^2 / 2 \sigma^2] # = - \ln [\sqrt{2 \pi} \sigma] values['f'] = - numpy.log(2 * numpy.pi * (sigma / units.angstroms)**2) * (3.0/2.0) return values
def runTest(path, systemName, platformName, eps, tolerance, precision, queue): """This function runs the test for a single system.""" print "\nsystemName =", systemName # load the system from the xml file system = loadXMLFile(path, systemName) # set Ewald error tolerance to a sufficiently small value so the requested accuracy will be achievable for f in system.getForces(): try: f.setEwaldErrorTolerance(tolerance/2) except: pass # read in the particle positions from the .pos file positions = loadPosFile(path, systemName) numParticles = len(positions) print "numParticles =", numParticles, "(from the .pos file)" integrator = mm.LangevinIntegrator(300*unit.kelvin, 1/unit.picosecond, 0.002*unit.picoseconds) print "mm.Platform.getNumPlatforms =", mm.Platform.getNumPlatforms() platform = mm.Platform.getPlatformByName(platformName) print "platform.getName() =", platform.getName() print "Building \'context\' on \'platform\'." context = mm.Context(system, integrator, platform, properties) context.setPositions(positions) state0 = context.getState(getForces=True) forces0 = state0.getForces() # make sure the size of the force vector is equal to the number of particles assert (len(forces0)==numParticles) # calculate the norm of the forces force0NormSum = 0.0*unit.kilojoules**2/unit.mole**2/unit.nanometer**2 for f in forces0: force0NormSum += unit.dot(f,f) force0Norm = unit.sqrt(force0NormSum) print "force0Norm =", force0Norm epsilon = eps*unit.nanometer step = epsilon/force0Norm print "step =", step # perturb the coordinates along the direction of forces0 and evaluate the energy context.setPositions([p-2*f*step for p,f in zip(positions, forces0)]) pe1 = context.getState(getEnergy=True).getPotentialEnergy() context.setPositions([p-f*step for p,f in zip(positions, forces0)]) pe2 = context.getState(getEnergy=True).getPotentialEnergy() context.setPositions([p+f*step for p,f in zip(positions, forces0)]) pe3 = context.getState(getEnergy=True).getPotentialEnergy() context.setPositions([p+2*f*step for p,f in zip(positions, forces0)]) pe4 = context.getState(getEnergy=True).getPotentialEnergy() # use a finite difference approximation to calculate the expected force0Norm expectedForceNorm = (-pe1+8*pe2-8*pe3+pe4)/(12*epsilon) relativeDifference = abs(expectedForceNorm-force0Norm)/force0Norm print "pe1 =", pe1 print "pe2 =", pe2 print "pe3 =", pe3 print "pe4 =", pe4 print "expectedForceNorm =", expectedForceNorm print "relativeDifference =", relativeDifference # check that the energy is within the desired range if relativeDifference > tolerance: print "*** ERROR EXCEEDS TOLERANCE ***" queue.put(False) else: queue.put(True) print "Test of", systemName, "complete." del(context)
def createUnfoldedSurrogate(topology, reference_system, locality=5): """ Create a surrogate for the unfolded state in which non-local residue interactions are excluded. Parameters ---------- topology : simtk.openmm.app.Topology The Topology object for the reference system. reference_system : simtk.openmm.System The System object for the reference system to be modified. locality : int, optional The number of residues beyond which nonbonded interactions will be excluded. Returns ------- system : simtk.openmm.System A System object corresponding to the desired construct. TODO ---- * Replace NonbondedForce with CustomBondForce for improved efficiency. * Replace CustomGBForce with CustomBondForce for improved efficiency. """ # Make a copy of the reference system if a working copy is not specified. system = copy.deepcopy(reference_system) # Get NonbondedForce objects for system copy. nonbonded_force = _findForces(system, 'NonbondedForce', first_only=True) gb_force = _findForces(system, 'CustomGBForce', first_only=True) # Add exclusions for non-local interactions. for atom1 in topology.atoms(): [charge1, sigma1, epsilon1] = nonbonded_force.getParticleParameters(atom1.index) for atom2 in topology.atoms(): if (atom1.index < atom2.index): if (abs(atom1.residue.index - atom2.residue.index) <= locality): # Create exclusion. try: [charge2, sigma2, epsilon2 ] = nonbonded_force.getParticleParameters(atom1.index) chargeprod = charge1 * charge2 sigma = 0.5 * (sigma1 + sigma2) epsilon = unit.sqrt(epsilon1 * epsilon2) nonbonded_force.addException(atom1.index, atom2.index, chargeprod, sigma, epsilon, False) except: # Exception already exists; don't modify it. pass else: # Exclude GB interactions between nonlocal particles. gb_force.addExclusion(atom1.index, atom2.index) # Turn off standard particle interactions. zero_charge = 0.0 * unit.elementary_charge unit_sigma = 1.0 * unit.angstroms zero_epsilon = 0.0 * unit.kilocalories_per_mole for atom in topology.atoms(): nonbonded_force.setParticleParameters(atom.index, zero_charge, unit_sigma, zero_epsilon) # Return modified system. return system
test_success = True for platform_index in range(openmm.Platform.getNumPlatforms()): try: platform = openmm.Platform.getPlatform(platform_index) platform_name = platform.getName() [platform_potential, platform_force] = compute_potential_and_force(system, positions, platform) # Compute error in potential. potential_error = platform_potential - reference_potential # Compute per-atom RMS (magnitude) and RMS error in force. force_unit = units.kilocalories_per_mole / units.nanometers natoms = system.getNumParticles() force_mse = (((reference_force - platform_force) / force_unit)**2).sum() / natoms * force_unit**2 force_rmse = units.sqrt(force_mse) force_ms = ((platform_force / force_unit)**2).sum() / natoms * force_unit**2 force_rms = units.sqrt(force_ms) print "%32s %16.6f kcal/mol %16.6f kcal/mol %16.6f kcal/mol %16.6f kcal/mol" % (platform_name, platform_potential / units.kilocalories_per_mole, potential_error / units.kilocalories_per_mole, force_rms / force_unit, force_rmse / force_unit) # Mark whether tolerance is exceeded or not. if abs(potential_error) > ENERGY_TOLERANCE: test_success = False print "%32s WARNING: Potential energy error (%.6f kcal/mol) exceeds tolerance (%.6f kcal/mol). Test failed." % ("", potential_error/units.kilocalories_per_mole, ENERGY_TOLERANCE/units.kilocalories_per_mole) if abs(force_rmse) > FORCE_RMSE_TOLERANCE: test_success = False print "%32s WARNING: Force RMS error (%.6f kcal/mol) exceeds tolerance (%.6f kcal/mol). Test failed." % ("", force_rmse/force_unit, FORCE_RMSE_TOLERANCE/force_unit) if debug: for atom_index in range(natoms):
def _addAtomsToTopology(self, heavyAtomsOnly, omitUnknownMolecules): """Create a new Topology in which missing atoms have been added. Parameters ---------- heavyAtomsOnly : bool If True, only heavy atoms will be added to the topology. omitUnknownMolecules : bool If True, unknown molecules will be omitted from the topology. Returns ------- newTopology : simtk.openmm.app.Topology A new Topology object containing atoms from the old. newPositions : list of simtk.unit.Quantity with units compatible with nanometers Atom positions for the new Topology object. newAtoms : simtk.openmm.app.Topology.Atom New atom objects. existingAtomMap : dict Mapping from old atoms to new atoms. """ newTopology = app.Topology() newPositions = []*unit.nanometer newAtoms = [] existingAtomMap = {} addedAtomMap = {} addedOXT = [] residueCenters = [self._computeResidueCenter(res).value_in_unit(unit.nanometers) for res in self.topology.residues()]*unit.nanometers for chain in self.topology.chains(): if omitUnknownMolecules and not any(residue.name in self.templates for residue in chain.residues()): continue chainResidues = list(chain.residues()) newChain = newTopology.addChain(chain.id) for indexInChain, residue in enumerate(chain.residues()): # Insert missing residues here. if (chain.index, indexInChain) in self.missingResidues: insertHere = self.missingResidues[(chain.index, indexInChain)] endPosition = self._computeResidueCenter(residue) if indexInChain > 0: startPosition = self._computeResidueCenter(chainResidues[indexInChain-1]) loopDirection = _findUnoccupiedDirection((startPosition+endPosition)/2, residueCenters) else: outward = _findUnoccupiedDirection(endPosition, residueCenters)*unit.nanometers norm = unit.norm(outward) if norm > 0*unit.nanometer: outward *= len(insertHere)*0.5*unit.nanometer/norm startPosition = endPosition+outward loopDirection = None firstIndex = int(residue.id)-len(insertHere) self._addMissingResiduesToChain(newChain, insertHere, startPosition, endPosition, loopDirection, residue, newAtoms, newPositions, firstIndex) # Create the new residue and add existing heavy atoms. newResidue = newTopology.addResidue(residue.name, newChain, residue.id) addResiduesAfter = (residue == chainResidues[-1] and (chain.index, indexInChain+1) in self.missingResidues) for atom in residue.atoms(): if not heavyAtomsOnly or (atom.element is not None and atom.element != hydrogen): if atom.name == 'OXT' and (chain.index, indexInChain+1) in self.missingResidues: continue # Remove terminal oxygen, since we'll add more residues after this one newAtom = newTopology.addAtom(atom.name, atom.element, newResidue) existingAtomMap[atom] = newAtom newPositions.append(self.positions[atom.index]) if residue in self.missingAtoms: # Find corresponding atoms in the residue and the template. template = self.templates[residue.name] atomPositions = dict((atom.name, self.positions[atom.index]) for atom in residue.atoms()) points1 = [] points2 = [] for atom in template.topology.atoms(): if atom.name in atomPositions: points1.append(atomPositions[atom.name].value_in_unit(unit.nanometer)) points2.append(template.positions[atom.index].value_in_unit(unit.nanometer)) # Compute the optimal transform to overlay them. (translate2, rotate, translate1) = _overlayPoints(points1, points2) # Add the missing atoms. addedAtomMap[residue] = {} for atom in self.missingAtoms[residue]: newAtom = newTopology.addAtom(atom.name, atom.element, newResidue) newAtoms.append(newAtom) addedAtomMap[residue][atom] = newAtom templatePosition = template.positions[atom.index].value_in_unit(unit.nanometer) newPositions.append((mm.Vec3(*np.dot(rotate, templatePosition+translate2))+translate1)*unit.nanometer) if residue in self.missingTerminals: terminalsToAdd = self.missingTerminals[residue] else: terminalsToAdd = None # If this is the end of the chain, add any missing residues that come after it. if residue == chainResidues[-1] and (chain.index, indexInChain+1) in self.missingResidues: insertHere = self.missingResidues[(chain.index, indexInChain+1)] if len(insertHere) > 0: startPosition = self._computeResidueCenter(residue) outward = _findUnoccupiedDirection(startPosition, residueCenters)*unit.nanometers norm = unit.norm(outward) if norm > 0*unit.nanometer: outward *= len(insertHere)*0.5*unit.nanometer/norm endPosition = startPosition+outward firstIndex = int(residue.id)+1 self._addMissingResiduesToChain(newChain, insertHere, startPosition, endPosition, None, residue, newAtoms, newPositions, firstIndex) newResidue = list(newChain.residues())[-1] if newResidue.name in proteinResidues: terminalsToAdd = ['OXT'] else: terminalsToAdd = None # If a terminal OXT is missing, add it. if terminalsToAdd is not None: atomPositions = dict((atom.name, newPositions[atom.index].value_in_unit(unit.nanometer)) for atom in newResidue.atoms()) if 'OXT' in terminalsToAdd: newAtom = newTopology.addAtom('OXT', oxygen, newResidue) newAtoms.append(newAtom) addedOXT.append(newAtom) d_ca_o = atomPositions['O']-atomPositions['CA'] d_ca_c = atomPositions['C']-atomPositions['CA'] d_ca_c /= unit.sqrt(unit.dot(d_ca_c, d_ca_c)) v = d_ca_o - d_ca_c*unit.dot(d_ca_c, d_ca_o) newPositions.append((atomPositions['O']+2*v)*unit.nanometer) newTopology.setUnitCellDimensions(self.topology.getUnitCellDimensions()) newTopology.createStandardBonds() newTopology.createDisulfideBonds(newPositions) # Return the results. return (newTopology, newPositions, newAtoms, existingAtomMap)
def run_dynamics(self, x0:np.ndarray, n_steps:int = 100, stepsize:unit.quantity.Quantity = 1.0*unit.femtosecond, collision_rate:unit.quantity.Quantity = 10/unit.picoseconds, progress_bar:bool = False ): """Unadjusted Langevin dynamics. Parameters ---------- x0 : array of floats, unit'd (distance unit) initial configuration force : callable, accepts a unit'd array and returns a unit'd array assumes input is in units of distance output is in units of energy / distance n_steps : integer number of Langevin steps stepsize : float > 0, in units of time finite timestep parameter collision_rate : float > 0, in units of 1/time controls the rate of interaction with the heat bath progress_bar : bool use tqdm to show progress bar Returns ------- traj : [n_steps + 1 x dim] array of floats, unit'd trajectory of samples generated by Langevin dynamics """ assert(type(x0) == unit.Quantity) assert(type(stepsize) == unit.Quantity) assert(type(collision_rate) == unit.Quantity) assert(type(self.temperature) == unit.Quantity) # generate mass arrays mass_dict_in_daltons = {'H': 1.0, 'C': 12.0, 'N': 14.0, 'O': 16.0} masses = np.array([mass_dict_in_daltons[a] for a in self.atom_list]) * unit.daltons sigma_v = np.array([unit.sqrt(kB * self.temperature / m) / speed_unit for m in masses]) * speed_unit v0 = np.random.randn(len(sigma_v),3) * sigma_v[:,None] # convert initial state numpy arrays with correct attached units x = np.array(x0.value_in_unit(distance_unit)) * distance_unit v = np.array(v0.value_in_unit(speed_unit)) * speed_unit # traj is accumulated as a list of arrays with attached units traj = [x] # dimensionless scalars a = np.exp(- collision_rate * stepsize) b = np.sqrt(1 - np.exp(-2 * collision_rate * stepsize)) # compute force on initial configuration F, E = self.force.calculate_force(x) # energy is saved as a list energy = [E] trange = range(n_steps) if progress_bar: trange = tqdm(trange) for _ in trange: # v v += (stepsize * 0.5) * F / masses[:,None] # r x += (stepsize * 0.5) * v # o v = (a * v) + (b * sigma_v[:,None] * np.random.randn(*x.shape)) # r x += (stepsize * 0.5) * v F, E = self.force.calculate_force(x) energy.append(E) # v v += (stepsize * 0.5) * F / masses[:,None] norm_F = np.linalg.norm(F) # report gradient norm if progress_bar: trange.set_postfix({'|force|': norm_F}) # check positions and forces are finite if (not np.isfinite(x).all()) or (not np.isfinite(norm_F)): print("Numerical instability encountered!") return traj traj.append(x) return traj, energy
def computeHarmonicOscillatorExpectations(K, mass, temperature): """ Compute moments of potential and kinetic energy distributions and free energies for harmonic oscillator. ARGUMENTS K - spring constant mass - mass of particle temperature - temperature RETURNS values (dict) - values['potential'] is a dict with 'mean' and 'stddev' of potential energy distribution; values['kinetic'] is a dict with 'mean' and 'stddev' of kinetic energy distribution; values['free energies'] is the free energy due to the 'potential', 'kinetic', or 'total' part of the partition function NOTES Numerical quadrature is used to evaluate the moments of the potential energy distribution. EXAMPLES >>> import simtk.unit as units >>> temperature = 298.0 * units.kelvin >>> sigma = 0.5 * units.angstroms # define standard deviation for harmonic oscillator >>> mass = 12.0 * units.amu # mass of harmonic oscillator >>> kT = kB * temperature # thermal energy >>> beta = 1.0 / kT # inverse temperature >>> K = kT / sigma**2 # spring constant consistent with variance sigma**2 >>> values = computeHarmonicOscillatorExpectations(K, mass, temperature) """ values = dict() # Compute thermal energy and inverse temperature from specified temperature. kB = units.BOLTZMANN_CONSTANT_kB * units.AVOGADRO_CONSTANT_NA kT = kB * temperature # thermal energy beta = 1.0 / kT # inverse temperature # Compute standard deviation along one dimension. sigma = 1.0 / units.sqrt(beta * K) # Define limits of integration along r. r_min = 0.0 * units.nanometers # initial value for integration r_max = 10.0 * sigma # maximum radius to integrate to # Compute mean and std dev of potential energy. import scipy.integrate V = lambda r : (K/2.0) * (r*units.nanometers)**2 / units.kilojoules_per_mole # potential in kJ/mol, where r in nm q = lambda r : 4.0 * math.pi * r**2 * math.exp(-beta * (K/2.0) * (r*units.nanometers)**2) # q(r), where r in nm (IqV2, dIqV2) = scipy.integrate.quad(lambda r : q(r) * V(r)**2, r_min / units.nanometers, r_max / units.nanometers) (IqV, dIqV) = scipy.integrate.quad(lambda r : q(r) * V(r), r_min / units.nanometers, r_max / units.nanometers) (Iq, dIq) = scipy.integrate.quad(lambda r : q(r), r_min / units.nanometers, r_max / units.nanometers) values['potential'] = dict() values['potential']['mean'] = (IqV / Iq) * units.kilojoules_per_mole values['potential']['stddev'] = (IqV2 / Iq) * units.kilojoules_per_mole # Compute mean and std dev of kinetic energy. values['kinetic'] = dict() values['kinetic']['mean'] = (3./2.) * kT values['kinetic']['stddev'] = math.sqrt(3./2.) * kT # Compute free energies. V0 = (units.liter / units.AVOGADRO_CONSTANT_NA / units.mole).in_units_of(units.angstroms**3) # standard state reference volume (1M) values['free energies'] = dict() values['free energies']['potential'] = - numpy.log((numpy.sqrt(2.0 * math.pi) * sigma)**3 / V0) values['free energies']['kinetic'] = (3./2.) values['free energies']['total'] = values['free energies']['potential'] + values['free energies']['kinetic'] return values
def _calculateElongation(self, state): positions = state.getPositions(asNumpy=True) displacement = positions[self._index1] - positions[self._index2] distance = unit.sqrt(unit.sum(displacement**2)) return distance.value_in_unit(unit.nanometers)
def check_harmonic_oscillator_ncmc(ncmc_nsteps=50, ncmc_integrator="VV"): """ Test NCMC switching of a 3D harmonic oscillator. In this test, the oscillator center is dragged in space, and we check the computed free energy difference with BAR, which should be 0. """ # Parameters for 3D harmonic oscillator mass = 39.948 * unit.amu # mass of particle (argon) sigma = 5.0 * unit.angstrom # standard deviation of harmonic oscillator collision_rate = 5.0 / unit.picosecond # collision rate temperature = 300.0 * unit.kelvin # temperature platform_name = 'Reference' # platform anme NSIGMA_MAX = 6.0 # number of standard errors away from analytical solution tolerated before Exception is thrown # Compute derived quantities. kT = kB * temperature # thermal energy beta = 1.0 / kT # inverse energy K = kT / sigma**2 # spring constant tau = 2 * math.pi * unit.sqrt(mass / K) # time constant timestep = tau / 20.0 platform = openmm.Platform.getPlatformByName(platform_name) # Create a 3D harmonic oscillator with context parameter controlling center of oscillator. system = openmm.System() system.addParticle(mass) energy_expression = '(K/2.0) * ((x-x0)^2 + y^2 + z^2);' force = openmm.CustomExternalForce(energy_expression) force.addGlobalParameter('K', K.in_unit_system(unit.md_unit_system)) force.addGlobalParameter('x0', 0.0) force.addParticle(0, []) system.addForce(force) # Set the positions at the origin. positions = unit.Quantity(np.zeros([1, 3], np.float32), unit.angstroms) functions = {'x0': 'lambda'} # drag spring center x0 from perses.annihilation.ncmc_integrator import NCMCVVAlchemicalIntegrator, NCMCGHMCAlchemicalIntegrator if ncmc_integrator == "VV": ncmc_insert = NCMCVVAlchemicalIntegrator( temperature, system, functions, direction='insert', nsteps=ncmc_nsteps, timestep=timestep) # 'insert' drags lambda from 0 -> 1 ncmc_delete = NCMCVVAlchemicalIntegrator( temperature, system, functions, direction='delete', nsteps=ncmc_nsteps, timestep=timestep) # 'insert' drags lambda from 0 -> 1 elif ncmc_integrator == "GHMC": ncmc_insert = NCMCGHMCAlchemicalIntegrator( temperature, system, functions, direction='insert', collision_rate=9.1 / unit.picoseconds, nsteps=ncmc_nsteps, timestep=timestep) # 'insert' drags lambda from 0 -> 1 ncmc_delete = NCMCGHMCAlchemicalIntegrator( temperature, system, functions, direction='delete', collision_rate=9.1 / unit.picoseconds, nsteps=ncmc_nsteps, timestep=timestep) # 'insert' drags lambda from 0 -> 1 else: raise Exception( "%s not recognized as integrator name. Options are VV and GHMC" % ncmc_integrator) # Run NCMC switching trials where the spring center is switched with lambda: 0 -> 1 over a finite number of steps. w_f = collect_switching_data(system, positions, functions, temperature, collision_rate, timestep, platform, ncmc_integrator=ncmc_insert, ncmc_nsteps=ncmc_nsteps, direction='insert') w_r = collect_switching_data(system, positions, functions, temperature, collision_rate, timestep, platform, ncmc_integrator=ncmc_delete, ncmc_nsteps=ncmc_nsteps, direction='delete') from pymbar import BAR [df, ddf] = BAR(w_f, w_r, method='self-consistent-iteration') print('%8.3f +- %.3f kT' % (df, ddf)) if (abs(df) > NSIGMA_MAX * ddf): msg = 'Delta F (%d steps switching) = %f +- %f kT; should be within %f sigma of 0' % ( ncmc_nsteps, df, ddf, NSIGMA_MAX) msg += '\n' msg += 'w_f = %s\n' % str(w_f) msg += 'w_r = %s\n' % str(w_r) raise Exception(msg)
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))