def _propagate_replicas(self): # Reset statistics for MC trial times. self.displacement_trial_time = 0.0 self.rotation_trial_time = 0.0 self.displacement_trials_accepted = 0 self.rotation_trials_accepted = 0 # Propagate replicas. ReplicaExchange._propagate_replicas(self) # Print summary statistics. # TODO: Streamline this idiom. if self.mpicomm: # MPI from mpi4py import MPI if self.mc_displacement and (self.mc_atoms is not None): self.displacement_trials_accepted = self.mpicomm.reduce(self.displacement_trials_accepted, op=MPI.SUM) self.displacement_trial_time = self.mpicomm.reduce(self.displacement_trial_time, op=MPI.SUM) if self.mpicomm.rank == 0: logger.debug("Displacement MC trial times consumed %.3f s aggregate (%d accepted)" % (self.displacement_trial_time, self.displacement_trials_accepted)) if self.mc_rotation and (self.mc_atoms is not None): self.rotation_trials_accepted = self.mpicomm.reduce(self.rotation_trials_accepted, op=MPI.SUM) self.rotation_trial_time = self.mpicomm.reduce(self.rotation_trial_time, op=MPI.SUM) if self.mpicomm.rank == 0: logger.debug("Rotation MC trial times consumed %.3f s aggregate (%d accepted)" % (self.rotation_trial_time, self.rotation_trials_accepted)) else: # SERIAL if self.mc_displacement and (self.mc_atoms is not None): logger.debug("Displacement MC trial times consumed %.3f s aggregate (%d accepted)" % (self.displacement_trial_time, self.displacement_trials_accepted)) if self.mc_rotation and (self.mc_atoms is not None): logger.debug("Rotation MC trial times consumed %.3f s aggregate (%d accepted)" % (self.rotation_trial_time, self.rotation_trials_accepted)) return
def resume(self, options=None): """ """ ReplicaExchange.resume(self, options=options) # # Cache Context and integrator. # # Use first state as reference state. state = self.states[0] # If temperature and pressure are specified, make sure MonteCarloBarostat is attached. if state.temperature and state.pressure: forces = { state.system.getForce(index).__class__.__name__ : state.system.getForce(index) for index in range(state.system.getNumForces()) } if 'MonteCarloAnisotropicBarostat' in forces: raise Exception('MonteCarloAnisotropicBarostat is unsupported.') if 'MonteCarloBarostat' in forces: barostat = forces['MonteCarloBarostat'] # Set temperature and pressure. barostat.setTemperature(state.temperature) barostat.setDefaultPressure(state.pressure) barostat.setRandomNumberSeed(int(np.random.randint(0, MAX_SEED))) else: # Create barostat and add it to the system if it doesn't have one already. barostat = openmm.MonteCarloBarostat(state.pressure, state.temperature) barostat.setRandomNumberSeed(int(np.random.randint(0, MAX_SEED))) state.system.addForce(barostat)
def _display_citations(self): ReplicaExchange._display_citations(self) yank_citations = """\ Chodera JD, Shirts MR, Wang K, Friedrichs MS, Eastman P, Pande VS, and Branson K. YANK: An extensible platform for GPU-accelerated free energy calculations. In preparation.""" print yank_citations print "" return
def _finalize(self): """ Do anything necessary to finish run except close files. """ ReplicaExchange._finalize(self) # Clean up cached context and integrator. if hasattr(self, 'context'): del self._context, self._integrator return
def create(self, reference_state, alchemical_states, positions, displacement_sigma=None, mc_atoms=None, options=None, metadata=None): """ Initialize a modified Hamiltonian exchange simulation object. Parameters ---------- reference_state : ThermodynamicState reference state containing all thermodynamic parameters and reference System object' alchemical_states : list of AlchemicalState list of alchemical states (one per replica) positions : simtk.unit.Quantity of numpy natoms x 3 with units length positions (or a list of positions objects) for initial assignment of replicas (will be used in round-robin assignment) displacement_sigma : simtk.unit.Quantity with units distance size of displacement trial for Monte Carlo displacements, if specified (default: 1 nm) ligand_atoms : list of int, optional, default=None atoms to use for trial displacements for translational and orientational Monte Carlo trials, if specified (all atoms if None) options : dict, optional, default=None Optional dict to use for specifying simulation options. Provided keywords will be matched to object variables to replace defaults. metadata : dict, optional, default=None metadata to store in a 'metadata' group in store file """ # If an empty set is specified for mc_atoms, set this to None. if mc_atoms is not None: if len(mc_atoms) == 0: mc_atoms = None # Store trial displacement magnitude and atoms to rotate in MC move. self.displacement_sigma = 1.0 * unit.nanometer if mc_atoms is not None: self.mc_atoms = np.array(mc_atoms) self.mc_displacement = True self.mc_rotation = True else: self.mc_atoms = None self.mc_displacement = False self.mc_rotation = False self.displacement_trials_accepted = 0 # number of MC displacement trials accepted self.rotation_trials_accepted = 0 # number of MC displacement trials accepted # Store reference system. self.reference_system = copy.deepcopy(reference_state.system) # TODO: Form metadata dict. # Initialize replica-exchange simlulation. states = list() for alchemical_state in alchemical_states: state = ThermodynamicState(system=self.reference_system, temperature=reference_state.temperature, pressure=reference_state.pressure) setattr(state, 'alchemical_state', copy.deepcopy(alchemical_state)) # attach alchemical state states.append(state) # Initialize replica-exchange simlulation. ReplicaExchange.create(self, states, positions, options=options, metadata=metadata) # Override title. self.title = 'Alchemical Hamiltonian exchange simulation created using HamiltonianExchange class of repex.py on %s' % time.asctime( time.localtime()) return
def create(self, reference_state, alchemical_states, positions, displacement_sigma=None, mc_atoms=None, options=None, metadata=None): """ Initialize a modified Hamiltonian exchange simulation object. Parameters ---------- reference_state : ThermodynamicState reference state containing all thermodynamic parameters and reference System object' alchemical_states : list of AlchemicalState list of alchemical states (one per replica) positions : simtk.unit.Quantity of numpy natoms x 3 with units length positions (or a list of positions objects) for initial assignment of replicas (will be used in round-robin assignment) displacement_sigma : simtk.unit.Quantity with units distance size of displacement trial for Monte Carlo displacements, if specified (default: 1 nm) ligand_atoms : list of int, optional, default=None atoms to use for trial displacements for translational and orientational Monte Carlo trials, if specified (all atoms if None) options : dict, optional, default=None Optional dict to use for specifying simulation options. Provided keywords will be matched to object variables to replace defaults. metadata : dict, optional, default=None metadata to store in a 'metadata' group in store file """ # If an empty set is specified for mc_atoms, set this to None. if mc_atoms is not None: if len(mc_atoms) == 0: mc_atoms = None # Store trial displacement magnitude and atoms to rotate in MC move. self.displacement_sigma = 1.0 * unit.nanometer if mc_atoms is not None: self.mc_atoms = np.array(mc_atoms) self.mc_displacement = True self.mc_rotation = True else: self.mc_atoms = None self.mc_displacement = False self.mc_rotation = False self.displacement_trials_accepted = 0 # number of MC displacement trials accepted self.rotation_trials_accepted = 0 # number of MC displacement trials accepted # Store reference system. self.reference_system = copy.deepcopy(reference_state.system) # TODO: Form metadata dict. # Initialize replica-exchange simlulation. states = list() for alchemical_state in alchemical_states: state = ThermodynamicState(system=self.reference_system, temperature=reference_state.temperature, pressure=reference_state.pressure) setattr(state, 'alchemical_state', copy.deepcopy(alchemical_state)) # attach alchemical state states.append(state) # Initialize replica-exchange simlulation. ReplicaExchange.create(self, states, positions, options=options, metadata=metadata) # Override title. self.title = 'Alchemical Hamiltonian exchange simulation created using HamiltonianExchange class of repex.py on %s' % time.asctime(time.localtime()) return
def __init__(self, temperature, nbins, store_filename, protocol=None, mm=None): """ Initialize a Hamiltonian exchange simulation object. ARGUMENTS store_filename (string) - name of NetCDF file to bind to for simulation output and checkpointing OPTIONAL ARGUMENTS protocol (dict) - Optional protocol to use for specifying simulation protocol as a dict. Provided keywords will be matched to object variables to replace defaults. NOTES von Mises distribution used for restraints http://en.wikipedia.org/wiki/Von_Mises_distribution """ import simtk.pyopenmm.extras.testsystems as testsystems # Create reference system and state. [system, coordinates] = testsystems.AlanineDipeptideImplicit() self.reference_system = system self.reference_state = repex.ThermodynamicState( system=system, temperature=temperature) self.nbins = nbins self.kT = (repex.kB * temperature) self.beta = 1.0 / self.kT self.delta = 360.0 / float( nbins) * units.degrees # bin spacing (angular) self.sigma = self.delta / 3.0 # standard deviation (angular) self.kappa = (self.sigma / units.radians)**( -2) # kappa parameter (unitless) # Create list of thermodynamic states with different bias potentials. states = list() # Create a state without a biasing potential. [system, coordinates] = testsystems.AlanineDipeptideImplicit() state = repex.ThermodynamicState(system=system, temperature=temperature) states.append(state) # Create states with biasing potentials. for phi_index in range(nbins): for psi_index in range(nbins): print "bin (%d,%d)" % (phi_index, psi_index) # Create system. [system, coordinates] = testsystems.AlanineDipeptideImplicit() # Add biasing potentials. phi0 = (float(phi_index) + 0.5) * self.delta - 180.0 * units.degrees psi0 = (float(psi_index) + 0.5) * self.delta - 180.0 * units.degrees force = openmm.CustomTorsionForce( '-kT * kappa * cos(theta - theta0)') force.addGlobalParameter('kT', self.kT / units.kilojoules_per_mole) force.addPerTorsionParameter('kappa') force.addPerTorsionParameter('theta0') force.addTorsion(4, 6, 8, 14, [self.kappa, phi0 / units.radians]) force.addTorsion(6, 8, 14, 16, [self.kappa, psi0 / units.radians]) system.addForce(force) # Add state. state = repex.ThermodynamicState(system=system, temperature=temperature) states.append(state) # Initialize replica-exchange simlulation. ReplicaExchange.__init__(self, states, coordinates, store_filename, protocol=protocol, mm=mm) # Override title. self.title = '2D umbrella sampling replica-exchange simulation created on %s' % time.asctime( time.localtime()) return
def test_velocity_assignment(mpicomm=None, verbose=True): """ Test Maxwell-Boltzmann velocity assignment subtroutine produces correct distribution, raising an exception if this test fails. """ # Stop here if not root node. if mpicomm and (mpicomm.rank != 0): return if verbose: print "Testing Maxwell-Boltzmann velocity assignment: ", # Make a list of all test system constructors. import testsystems # Test parameters temperature = 298.0 * units.kelvin # test temperature kT = kB * temperature # thermal energy ntrials = 1000 # number of test trials systems_to_test = ['HarmonicOscillator', 'HarmonicOscillatorArray', 'AlanineDipeptideImplicit'] # systems to test for system_name in systems_to_test: #print '*' * 80 #print system_name # Create system. constructor = getattr(testsystems, system_name) [system, coordinates] = constructor() # Create temporary filename. import tempfile # use a temporary file for testing file = tempfile.NamedTemporaryFile() store_filename = file.name # Create repex instance. from repex import ReplicaExchange from thermodynamics import ThermodynamicState states = [ ThermodynamicState(system, temperature=temperature) ] simulation = ReplicaExchange(states=states, coordinates=coordinates, store_filename=store_filename) # Create integrator and context. natoms = system.getNumParticles() velocity_trials = numpy.zeros([ntrials, natoms, 3]) kinetic_energy_trials = numpy.zeros([ntrials]) for trial in range(ntrials): velocities = simulation._assign_Maxwell_Boltzmann_velocities(system, temperature) kinetic_energy = 0.5 * units.sum(units.sum(system.masses * velocities**2)) velocity_trials[trial,:,:] = velocities / (units.nanometers / units.picosecond) kinetic_energy_trials[trial] = kinetic_energy / units.kilocalories_per_mole velocity_mean = velocity_trials.mean(0) velocity_stderr = velocity_trials.std(0) / numpy.sqrt(ntrials) kinetic_analytical = (3.0/2.0) * natoms * kT / units.kilocalories_per_mole kinetic_mean = kinetic_energy_trials.mean() kinetic_error = kinetic_mean - kinetic_analytical kinetic_stderr = kinetic_energy_trials.std() / numpy.sqrt(ntrials) # Test if violations exceed tolerance. MAX_SIGMA = 6.0 # maximum number of standard errors allowed if numpy.any(numpy.abs(kinetic_error / kinetic_stderr) > MAX_SIGMA): print "analytical kinetic energy" print kinetic_analytical print "mean kinetic energy (kcal/mol)" print kinetic_mean print "difference (kcal/mol)" print kinetic_mean - kinetic_analytical print "stderr (kcal/mol)" print kinetic_stderr print "nsigma" print (kinetic_mean - kinetic_analytical) / kinetic_stderr raise Exception("Mean kinetic energy exceeds error tolerance of %.1f standard errors." % MAX_SIGMA) if numpy.any(numpy.abs(velocity_mean / velocity_stderr) > MAX_SIGMA): print "mean velocity (nm/ps)" print velocity_mean print "stderr (nm/ps)" print velocity_stderr print "nsigma" print velocity_mean / velocity_stderr raise Exception("Mean velocity exceeds error tolerance of %.1f standard errors." % MAX_SIGMA) if verbose: print "PASSED" return
def test_replica_exchange(mpicomm=None, verbose=True): """ Test that free energies and avergae potential energies of a 3D harmonic oscillator are correctly computed. TODO * Test ParallelTempering and HamiltonianExchange subclasses as well. * Test with different combinations of input parameters. """ if verbose and ((not mpicomm) or (mpicomm.rank==0)): print "Testing replica exchange facility with harmonic oscillators: ", # Create test system of harmonic oscillators import testsystems [system, coordinates] = testsystems.HarmonicOscillatorArray() # Define mass of carbon atom. mass = 12.0 * units.amu # Define thermodynamic states. from thermodynamics import ThermodynamicState states = list() # thermodynamic states sigmas = [0.2, 0.3, 0.4] * units.angstroms # standard deviations: beta K = 1/sigma^2 so K = 1/(beta sigma^2) temperatures = [300.0, 350.0, 400.0] * units.kelvin # temperatures seed_positions = list() analytical_results = list() f_i_analytical = list() # dimensionless free energies u_i_analytical = list() # reduced potential for (sigma, temperature) in zip(sigmas, temperatures): # Compute corresponding spring constant. kB = units.BOLTZMANN_CONSTANT_kB * units.AVOGADRO_CONSTANT_NA kT = kB * temperature # thermal energy beta = 1.0 / kT # inverse temperature K = 1.0 / (beta * sigma**2) # Create harmonic oscillator system. [system, positions] = testsystems.HarmonicOscillator(K=K, mass=mass, mm=openmm) # Create thermodynamic state. state = ThermodynamicState(system=system, temperature=temperature) # Append thermodynamic state and positions. states.append(state) seed_positions.append(positions) # Store analytical results. results = computeHarmonicOscillatorExpectations(K, mass, temperature) analytical_results.append(results) f_i_analytical.append(results['f']) reduced_potential = results['potential']['mean'] / kT u_i_analytical.append(reduced_potential) # Compute analytical Delta_f_ij nstates = len(f_i_analytical) f_i_analytical = numpy.array(f_i_analytical) u_i_analytical = numpy.array(u_i_analytical) s_i_analytical = u_i_analytical - f_i_analytical Delta_f_ij_analytical = numpy.zeros([nstates,nstates], numpy.float64) Delta_u_ij_analytical = numpy.zeros([nstates,nstates], numpy.float64) Delta_s_ij_analytical = numpy.zeros([nstates,nstates], numpy.float64) for i in range(nstates): for j in range(nstates): Delta_f_ij_analytical[i,j] = f_i_analytical[j] - f_i_analytical[i] Delta_u_ij_analytical[i,j] = u_i_analytical[j] - u_i_analytical[i] Delta_s_ij_analytical[i,j] = s_i_analytical[j] - s_i_analytical[i] # Define file for temporary storage. import tempfile # use a temporary file file = tempfile.NamedTemporaryFile(delete=False) store_filename = file.name #print "node %d : Storing data in temporary file: %s" % (mpicomm.rank, str(store_filename)) # DEBUG # Create and configure simulation object. from repex import ReplicaExchange simulation = ReplicaExchange(states, seed_positions, store_filename, mpicomm=mpicomm) # initialize the replica-exchange simulation simulation.number_of_iterations = 1000 # set the simulation to only run 2 iterations simulation.timestep = 2.0 * units.femtoseconds # set the timestep for integration simulation.nsteps_per_iteration = 500 # run 500 timesteps per iteration simulation.collision_rate = 0.001 / units.picosecond # DEBUG: Use a low collision rate simulation.platform = openmm.Platform.getPlatformByName('Reference') # use reference platform simulation.verbose = True # DEBUG # Run simulation. simulation.run() # run the simulation # Stop here if not root node. if mpicomm and (mpicomm.rank != 0): return # Analyze simulation to compute free energies. analysis = simulation.analyze() # TODO: Check if deviations exceed tolerance. Delta_f_ij = analysis['Delta_f_ij'] dDelta_f_ij = analysis['dDelta_f_ij'] error = Delta_f_ij - Delta_f_ij_analytical indices = numpy.where(dDelta_f_ij > 0.0) nsigma = numpy.zeros([nstates,nstates], numpy.float32) nsigma[indices] = error[indices] / dDelta_f_ij[indices] MAX_SIGMA = 6.0 # maximum allowed number of standard errors if numpy.any(nsigma > MAX_SIGMA): print "Delta_f_ij" print Delta_f_ij print "Delta_f_ij_analytical" print Delta_f_ij_analytical print "error" print error print "stderr" print dDelta_f_ij print "nsigma" print nsigma raise Exception("Dimensionless free energy difference exceeds MAX_SIGMA of %.1f" % MAX_SIGMA) error = analysis['Delta_u_ij'] - Delta_u_ij_analytical nsigma = numpy.zeros([nstates,nstates], numpy.float32) nsigma[indices] = error[indices] / dDelta_f_ij[indices] if numpy.any(nsigma > MAX_SIGMA): print "Delta_u_ij" print analysis['Delta_u_ij'] print "Delta_u_ij_analytical" print Delta_u_ij_analytical print "error" print error print "nsigma" print nsigma raise Exception("Dimensionless potential energy difference exceeds MAX_SIGMA of %.1f" % MAX_SIGMA) if verbose: print "PASSED." return
def __init__(self, temperature, nbins, store_filename, protocol=None, mm=None): """ Initialize a Hamiltonian exchange simulation object. ARGUMENTS store_filename (string) - name of NetCDF file to bind to for simulation output and checkpointing OPTIONAL ARGUMENTS protocol (dict) - Optional protocol to use for specifying simulation protocol as a dict. Provided keywords will be matched to object variables to replace defaults. NOTES von Mises distribution used for restraints http://en.wikipedia.org/wiki/Von_Mises_distribution """ import simtk.pyopenmm.extras.testsystems as testsystems # Create reference system and state. [system, coordinates] = testsystems.AlanineDipeptideImplicit() self.reference_system = system self.reference_state = repex.ThermodynamicState(system=system, temperature=temperature) self.nbins = nbins self.kT = (repex.kB * temperature) self.beta = 1.0 / self.kT self.delta = 360.0 / float(nbins) * units.degrees # bin spacing (angular) self.sigma = self.delta/3.0 # standard deviation (angular) self.kappa = (self.sigma / units.radians)**(-2) # kappa parameter (unitless) # Create list of thermodynamic states with different bias potentials. states = list() # Create a state without a biasing potential. [system, coordinates] = testsystems.AlanineDipeptideImplicit() state = repex.ThermodynamicState(system=system, temperature=temperature) states.append(state) # Create states with biasing potentials. for phi_index in range(nbins): for psi_index in range(nbins): print "bin (%d,%d)" % (phi_index, psi_index) # Create system. [system, coordinates] = testsystems.AlanineDipeptideImplicit() # Add biasing potentials. phi0 = (float(phi_index) + 0.5) * self.delta - 180.0 * units.degrees psi0 = (float(psi_index) + 0.5) * self.delta - 180.0 * units.degrees force = openmm.CustomTorsionForce('-kT * kappa * cos(theta - theta0)') force.addGlobalParameter('kT', self.kT / units.kilojoules_per_mole) force.addPerTorsionParameter('kappa') force.addPerTorsionParameter('theta0') force.addTorsion(4, 6, 8, 14, [self.kappa, phi0 / units.radians]) force.addTorsion(6, 8, 14, 16, [self.kappa, psi0 / units.radians]) system.addForce(force) # Add state. state = repex.ThermodynamicState(system=system, temperature=temperature) states.append(state) # Initialize replica-exchange simlulation. ReplicaExchange.__init__(self, states, coordinates, store_filename, protocol=protocol, mm=mm) # Override title. self.title = '2D umbrella sampling replica-exchange simulation created on %s' % time.asctime(time.localtime()) return
import tempfile file = tempfile.NamedTemporaryFile() # use a temporary file for testing -- you will want to keep this file, since it stores output and checkpoint data # Select platform: one of 'Reference' (CPU-only), 'Cuda' (NVIDIA Cuda), or 'OpenCL' (for OS X 10.6 with OpenCL OpenMM compiled) platform = simtk.openmm.Platform.getPlatformByName("OpenCL") platform = simtk.openmm.Platform.getPlatformByName("Cuda") # Set up device to bind to. print "Selecting MPI communicator and selecting a GPU device..." from mpi4py import MPI # MPI wrapper hostname = os.uname()[1] ngpus = 6 # number of GPUs per system comm = MPI.COMM_WORLD # MPI communicator deviceid = comm.rank % ngpus # select a unique GPU for this node assuming block allocation (not round-robin) platform.setPropertyDefaultValue('CudaDeviceIndex', '%d' % deviceid) # select Cuda device index platform.setPropertyDefaultValue('OpenCLDeviceIndex', '%d' % deviceid) # select OpenCL device index print "node '%s' deviceid %d / %d, MPI rank %d / %d" % (hostname, deviceid, ngpus, comm.rank, comm.size) # Make sure random number generators have unique seeds. seed = numpy.random.randint(sys.maxint - comm.size) + comm.rank numpy.random.seed(seed) # Set up replica exchange simulation. simulation = ReplicaExchange(states, coordinates, file.name, mpicomm=comm) # initialize the replica-exchange simulation simulation.verbose = True simulation.number_of_iterations = 100 # set the simulation to only run 2 iterations simulation.timestep = 2.0 * units.femtoseconds # set the timestep for integration simulation.nsteps_per_iteration = 500 # run 500 timesteps per iteration simulation.platform = platform simulation.run() # run the simulation