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. HamiltonianExchange._propagate_replicas(self) # Print summary statistics. if (self.mc_displacement or self.mc_rotation): if self.mpicomm: from mpi4py import MPI self.displacement_trials_accepted = self.mpicomm.reduce( self.displacement_trials_accepted, op=MPI.SUM) self.rotation_trials_accepted = self.mpicomm.reduce( self.rotation_trials_accepted, op=MPI.SUM) if self.verbose: total_mc_time = self.displacement_trial_time + self.rotation_trial_time print "Rotation and displacement MC trial times consumed %.3f s (%d translation | %d rotation accepted)" % ( total_mc_time, self.displacement_trials_accepted, self.rotation_trials_accepted) return
def _display_citations(self): HamiltonianExchange._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 create(self, reference_state, systems, positions, displacement_sigma=None, mc_atoms=None, options=None, mm=None, mpicomm=None, metadata=None): """ Initialize a modified Hamiltonian exchange simulation object. Parameters ---------- reference_state : ThermodynamicState reference state containing all thermodynamic parameters except the system, which will be replaced by 'systems' systems : list of simtk.openmm.System list of systems to simulate (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. """ # 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 * units.nanometer if mc_atoms is not None: self.mc_atoms = numpy.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 # Form metadata dict. # Initialize replica-exchange simlulation. HamiltonianExchange.create(self, reference_state, systems, positions, options=options, metadata=metadata) # Override title. self.title = 'Modified Hamiltonian exchange simulation created using HamiltonianExchange class of repex.py on %s' % time.asctime(time.localtime()) return
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. HamiltonianExchange._propagate_replicas(self) # Print summary statistics. if (self.mc_displacement or self.mc_rotation): if self.mpicomm: from mpi4py import MPI self.displacement_trials_accepted = self.mpicomm.reduce(self.displacement_trials_accepted, op=MPI.SUM) self.rotation_trials_accepted = self.mpicomm.reduce(self.rotation_trials_accepted, op=MPI.SUM) if self.verbose: total_mc_time = self.displacement_trial_time + self.rotation_trial_time print "Rotation and displacement MC trial times consumed %.3f s (%d translation | %d rotation accepted)" % (total_mc_time, self.displacement_trials_accepted, self.rotation_trials_accepted) return
def _propagate_replica(self, replica_index): """ Attempt a Monte Carlo rotation/translation move. """ # Attempt a Monte Carlo rotation/translation move. import numpy.random # Retrieve state. state_index = self.replica_states[replica_index] # index of thermodynamic state that current replica is assigned to state = self.states[state_index] # thermodynamic state # Retrieve integrator and context from thermodynamic state. integrator = state._integrator context = state._context # Attempt gaussian trial displacement with stddev 'self.displacement_sigma'. # TODO: Can combine these displacements and/or use cached potential energies to speed up this phase. # TODO: Break MC displacement and rotation into member functions and write separate unit tests. if self.mc_displacement and (self.mc_atoms is not None): initial_time = time.time() # Store original positions and energy. original_positions = self.replica_positions[replica_index] u_old = state.reduced_potential(original_positions) # Make symmetric Gaussian trial displacement of ligand. perturbed_positions = self.propose_displacement(self.displacement_sigma, original_positions, self.mc_atoms) u_new = state.reduced_potential(perturbed_positions) # Accept or reject with Metropolis criteria. du = u_new - u_old if (du <= 0.0) or (numpy.random.rand() < numpy.exp(-du)): self.displacement_trials_accepted += 1 self.replica_positions[replica_index] = perturbed_positions #print "translation du = %f (%d)" % (du, self.displacement_trials_accepted) # Print timing information. final_time = time.time() elapsed_time = final_time - initial_time self.displacement_trial_time += elapsed_time # Attempt random rotation of ligand. if self.mc_rotation and (self.mc_atoms is not None): initial_time = time.time() # Store original positions and energy. original_positions = self.replica_positions[replica_index] u_old = state.reduced_potential(original_positions) # Compute new potential. perturbed_positions = self.propose_rotation(original_positions, self.mc_atoms) u_new = state.reduced_potential(perturbed_positions) du = u_new - u_old if (du <= 0.0) or (numpy.random.rand() < numpy.exp(-du)): self.rotation_trials_accepted += 1 self.replica_positions[replica_index] = perturbed_positions #print "rotation du = %f (%d)" % (du, self.rotation_trials_accepted) # Accumulate timing information. final_time = time.time() elapsed_time = final_time - initial_time self.rotation_trial_time += elapsed_time # Propagate with Langevin dynamics as usual. HamiltonianExchange._propagate_replica(self, replica_index) return
protocol = factory.defaultComplexProtocolImplicit() # Create the perturbed systems using this protocol. systems = factory.createPerturbedSystems(protocol, verbose=True) # # Set up replica exchange simulation. # print "Setting up replica-exchange simulation..." # Create reference state. from thermodynamics import ThermodynamicState reference_state = ThermodynamicState(reference_system, temperature=temperature, pressure=pressure) # Create simulation. from repex import HamiltonianExchange simulation = HamiltonianExchange(reference_state, systems, positions, store_filename) simulation.number_of_iterations = niterations # set the simulation to only run 2 iterations simulation.timestep = timestep # set the timestep for integration simulation.nsteps_per_iteration = nsteps # run 50 timesteps per iteration simulation.minimize = False simulation.verbose = True simulation.platform = platform # set platform # Run simulation. print "Running simulation..." simulation.run() # run the simulation
def test_hamiltonian_exchange(mpi=None, verbose=True): """ Test that free energies and avergae potential energies of a 3D harmonic oscillator are correctly computed when running HamiltonianExchange. TODO * Integrate with test_replica_exchange. * Test with different combinations of input parameters. """ if verbose and ((not mpicomm) or (mpicomm.rank==0)): print "Testing Hamiltonian 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. sigmas = [0.2, 0.3, 0.4] * units.angstroms # standard deviations: beta K = 1/sigma^2 so K = 1/(beta sigma^2) temperature = 300.0 * units.kelvin # temperatures seed_positions = list() analytical_results = list() f_i_analytical = list() # dimensionless free energies u_i_analytical = list() # reduced potential systems = list() # Systems list for HamiltonianExchange for sigma in sigmas: # 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) # Append to systems list. systems.append(system) # Append positions. 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 "Storing data in temporary file: %s" % str(store_filename) # Create reference thermodynamic state. from thermodynamics import ThermodynamicState reference_state = ThermodynamicState(systems[0], temperature=temperature) # Create and configure simulation object. from repex import HamiltonianExchange simulation = HamiltonianExchange(reference_state, systems, 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 = 9.2 / units.picosecond simulation.platform = openmm.Platform.getPlatformByName('Reference') # use reference platform # 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 create(self, reference_state, systems, positions, displacement_sigma=None, mc_atoms=None, options=None, mm=None, mpicomm=None, metadata=None): """ Initialize a modified Hamiltonian exchange simulation object. Parameters ---------- reference_state : ThermodynamicState reference state containing all thermodynamic parameters except the system, which will be replaced by 'systems' systems : list of simtk.openmm.System list of systems to simulate (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. """ # 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 * units.nanometer if mc_atoms is not None: self.mc_atoms = numpy.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 # Form metadata dict. # Initialize replica-exchange simlulation. HamiltonianExchange.create(self, reference_state, systems, positions, options=options, metadata=metadata) # Override title. self.title = 'Modified Hamiltonian exchange simulation created using HamiltonianExchange class of repex.py on %s' % time.asctime( time.localtime()) return
def _propagate_replica(self, replica_index): """ Attempt a Monte Carlo rotation/translation move. """ # Attempt a Monte Carlo rotation/translation move. import numpy.random # Retrieve state. state_index = self.replica_states[ replica_index] # index of thermodynamic state that current replica is assigned to state = self.states[state_index] # thermodynamic state # Retrieve integrator and context from thermodynamic state. integrator = state._integrator context = state._context # Attempt gaussian trial displacement with stddev 'self.displacement_sigma'. # TODO: Can combine these displacements and/or use cached potential energies to speed up this phase. # TODO: Break MC displacement and rotation into member functions and write separate unit tests. if self.mc_displacement and (self.mc_atoms is not None): initial_time = time.time() # Store original positions and energy. original_positions = self.replica_positions[replica_index] u_old = state.reduced_potential(original_positions) # Make symmetric Gaussian trial displacement of ligand. perturbed_positions = self.propose_displacement( self.displacement_sigma, original_positions, self.mc_atoms) u_new = state.reduced_potential(perturbed_positions) # Accept or reject with Metropolis criteria. du = u_new - u_old if (du <= 0.0) or (numpy.random.rand() < numpy.exp(-du)): self.displacement_trials_accepted += 1 self.replica_positions[replica_index] = perturbed_positions #print "translation du = %f (%d)" % (du, self.displacement_trials_accepted) # Print timing information. final_time = time.time() elapsed_time = final_time - initial_time self.displacement_trial_time += elapsed_time # Attempt random rotation of ligand. if self.mc_rotation and (self.mc_atoms is not None): initial_time = time.time() # Store original positions and energy. original_positions = self.replica_positions[replica_index] u_old = state.reduced_potential(original_positions) # Compute new potential. perturbed_positions = self.propose_rotation( original_positions, self.mc_atoms) u_new = state.reduced_potential(perturbed_positions) du = u_new - u_old if (du <= 0.0) or (numpy.random.rand() < numpy.exp(-du)): self.rotation_trials_accepted += 1 self.replica_positions[replica_index] = perturbed_positions #print "rotation du = %f (%d)" % (du, self.rotation_trials_accepted) # Accumulate timing information. final_time = time.time() elapsed_time = final_time - initial_time self.rotation_trial_time += elapsed_time # Propagate with Langevin dynamics as usual. HamiltonianExchange._propagate_replica(self, replica_index) return