def _restore_thermodynamic_states(self, ncfile): """ Restore the thermodynamic states from a NetCDF file. """ logger.debug("Restoring thermodynamic states from NetCDF file...") initial_time = time.time() # Make sure this NetCDF file contains thermodynamic state information. if not 'thermodynamic_states' in ncfile.groups: raise Exception("Could not restore thermodynamic states from %s" % self.store_filename) # Create a group to store state information. ncgrp_stateinfo = ncfile.groups['thermodynamic_states'] # Get number of states. self.nstates = ncgrp_stateinfo.variables['nstates'].getValue() # Read thermodynamic state information. self.states = list() # Read reference system self.reference_system = self.mm.System() self.reference_system.__setstate__( str(ncgrp_stateinfo.variables['reference_system'][0])) # Read other parameters. for state_index in range(self.nstates): # Populate a new ThermodynamicState object. state = ThermodynamicState() # Read temperature. state.temperature = float(ncgrp_stateinfo.variables['temperatures'] [state_index]) * unit.kelvin # Read pressure, if present. if 'pressures' in ncgrp_stateinfo.variables: state.pressure = float(ncgrp_stateinfo.variables['pressures'] [state_index]) * unit.atmospheres # Read alchemical states. state.alchemical_state = AlchemicalState() for key in ncfile.groups['alchemical_states'].variables.keys(): state.alchemical_state[key] = float( ncfile.groups['alchemical_states'].variables[key] [state_index]) # Set System object (which points to reference system). state.system = self.reference_system # Store state. self.states.append(state) final_time = time.time() elapsed_time = final_time - initial_time logger.debug( "Restoring thermodynamic states from NetCDF file took %.3f s." % elapsed_time) return True
def benchmark(reference_system, positions, platform_name=None, nsteps=500, timestep=1.0 * unit.femtoseconds, factory_args=None): """ Benchmark performance of alchemically modified system relative to original system. Parameters ---------- reference_system : simtk.openmm.System The reference System object to compare with positions : simtk.unit.Quantity with units compatible with nanometers The positions to assess energetics for. platform_name : str, optional, default=None The name of the platform to use for benchmarking. nsteps : int, optional, default=500 Number of molecular dynamics steps to use for benchmarking. timestep : simtk.unit.Quantity with units compatible with femtoseconds, optional, default=1*femtoseconds Timestep to use for benchmarking. factory_args : dict(), optional, default=None Arguments passed to AbsoluteAlchemicalFactory. """ from alchemy import AbsoluteAlchemicalFactory, AlchemicalState # Create a factory to produce alchemical intermediates. print("Creating alchemical factory...") initial_time = time.time() factory = AbsoluteAlchemicalFactory(reference_system, **factory_args) final_time = time.time() elapsed_time = final_time - initial_time print("AbsoluteAlchemicalFactory initialization took %.3f s" % elapsed_time) # Create an alchemically-perturbed state corresponding to nearly fully-interacting. # NOTE: We use a lambda slightly smaller than 1.0 because the AlchemicalFactory does not use Custom*Force softcore versions if lambda = 1.0 identically. lambda_value = 1.0 - 1.0e-6 alchemical_state = AlchemicalState(lambda_electrostatics=lambda_value, lambda_sterics=lambda_value, lambda_torsions=lambda_value) platform = None if platform_name: platform = openmm.Platform.getPlatformByName(platform_name) temperature = 300. * unit.kelvin collision_rate = 90. / unit.picoseconds # Create the perturbed system. print("Creating alchemically-modified state...") initial_time = time.time() alchemical_system = factory.createPerturbedSystem(alchemical_state) final_time = time.time() elapsed_time = final_time - initial_time # Compare energies. print("Computing reference energies...") reference_integrator = openmm.LangevinIntegrator(temperature, collision_rate, timestep) if platform: reference_context = openmm.Context(reference_system, reference_integrator, platform) else: reference_context = openmm.Context(reference_system, reference_integrator) reference_context.setPositions(positions) reference_state = reference_context.getState(getEnergy=True) reference_potential = reference_state.getPotentialEnergy() print(reference_potential) print("Computing alchemical energies...") alchemical_integrator = openmm.LangevinIntegrator(temperature, collision_rate, timestep) if platform: alchemical_context = openmm.Context(alchemical_system, alchemical_integrator, platform) else: alchemical_context = openmm.Context(alchemical_system, alchemical_integrator) alchemical_context.setPositions(positions) alchemical_state = alchemical_context.getState(getEnergy=True) alchemical_potential = alchemical_state.getPotentialEnergy() print(reference_potential) # Make sure all kernels are compiled. reference_integrator.step(2) alchemical_integrator.step(2) # Time simulations. print("Simulating reference system...") initial_time = time.time() reference_integrator.step(nsteps) reference_state = reference_context.getState() #reference_state = reference_context.getState(getEnergy=True) #reference_potential = reference_state.getPotentialEnergy() final_time = time.time() reference_time = final_time - initial_time print("Simulating alchemical system...") initial_time = time.time() alchemical_integrator.step(nsteps) reference_state = alchemical_context.getState() #alchemical_state = alchemical_context.getState(getEnergy=True) #alchemical_potential = alchemical_state.getPotentialEnergy() final_time = time.time() alchemical_time = final_time - initial_time seconds_per_day = (1. * unit.day) / (1. * unit.seconds) print("TIMINGS") print( "reference system : %12.3f s for %8d steps (%12.3f ms/step; %12.3f ns/day)" % (reference_time, nsteps, reference_time / nsteps * 1000, nsteps * timestep * (seconds_per_day / reference_time) / unit.nanoseconds)) print( "alchemical system : %12.3f s for %8d steps (%12.3f ms/step; %12.3f ns/day)" % (alchemical_time, nsteps, alchemical_time / nsteps * 1000, nsteps * timestep * (seconds_per_day / alchemical_time) / unit.nanoseconds)) print("alchemical simulation is %12.3f x slower than unperturbed system" % (alchemical_time / reference_time))
# Create alchemical intermediates. #============================================================================== from alchemy import AlchemicalState, AbsoluteAlchemicalFactory print "Creating alchemical intermediates..." alchemical_atoms = range(nfixed) # atoms to be alchemically modified alchemical_states = list( ) # alchemical_states[istate] is the alchemical state lambda specification for alchemical state 'istate' # Create alchemical states where we turn on Lennard-Jones (via softcore) with zero charge. for vdw_lambda in vdw_lambdas: alchemical_states.append( AlchemicalState(coulomb_lambda=0.0, vdw_lambda=vdw_lambda, annihilate_coulomb=True, annihilate_vdw=True)) # Create alchemical states where we turn on charges with full Lennard-Jones. for coulomb_lambda in coulomb_lambdas: alchemical_states.append( AlchemicalState(coulomb_lambda=coulomb_lambda, vdw_lambda=1.0, annihilate_coulomb=True, annihilate_vdw=True)) alchemical_factory = AbsoluteAlchemicalFactory( reference_system, alchemical_atoms=alchemical_atoms) systems = alchemical_factory.createPerturbedSystems( alchemical_states ) # systems[istate] will be the System object corresponding to alchemical intermediate state index 'istate'
def testAlchemicalFactory(reference_system, coordinates, receptor_atoms, ligand_atoms, platform_name='Reference', annihilateElectrostatics=True, annihilateLennardJones=False): """ Compare energies of reference system and fully-interacting alchemically modified system. ARGUMENTS reference_system (simtk.openmm.System) - the reference System object to compare with coordinates - the coordinates to assess energetics for receptor_atoms (list of int) - the list of receptor atoms ligand_atoms (list of int) - the list of ligand atoms to alchemically modify """ import simtk.unit as units import simtk.openmm as openmm import time # Create a factory to produce alchemical intermediates. print "Creating alchemical factory..." initial_time = time.time() factory = AbsoluteAlchemicalFactory(reference_system, ligand_atoms=ligand_atoms) final_time = time.time() elapsed_time = final_time - initial_time print "AbsoluteAlchemicalFactory initialization took %.3f s" % elapsed_time # Create an alchemically-perturbed state corresponding to nearly fully-interacting. # NOTE: We use a lambda slightly smaller than 1.0 because the AlchemicalFactory does not use Custom*Force softcore versions if lambda = 1.0 identically. lambda_value = 1.0 - 1.0e-6 alchemical_state = AlchemicalState(0.00, lambda_value, lambda_value, lambda_value) alchemical_state.annihilateElectrostatics = annihilateElectrostatics alchemical_state.annihilateLennardJones = annihilateLennardJones #platform_name = 'Reference' # DEBUG platform = openmm.Platform.getPlatformByName(platform_name) # Create the perturbed system. print "Creating alchemically-modified state..." initial_time = time.time() alchemical_system = factory.createPerturbedSystem(alchemical_state) final_time = time.time() elapsed_time = final_time - initial_time # Compare energies. timestep = 1.0 * units.femtosecond print "Computing reference energies..." reference_integrator = openmm.VerletIntegrator(timestep) reference_context = openmm.Context(reference_system, reference_integrator, platform) reference_context.setPositions(coordinates) reference_state = reference_context.getState(getEnergy=True) reference_potential = reference_state.getPotentialEnergy() print "Computing alchemical energies..." alchemical_integrator = openmm.VerletIntegrator(timestep) alchemical_context = openmm.Context(alchemical_system, alchemical_integrator, platform) alchemical_context.setPositions(coordinates) alchemical_state = alchemical_context.getState(getEnergy=True) alchemical_potential = alchemical_state.getPotentialEnergy() delta = alchemical_potential - reference_potential print "reference system : %24.8f kcal/mol" % (reference_potential / units.kilocalories_per_mole) print "alchemically modified : %24.8f kcal/mol" % (alchemical_potential / units.kilocalories_per_mole) print "ERROR : %24.8f kcal/mol" % ((alchemical_potential - reference_potential) / units.kilocalories_per_mole) print "elapsed alchemical time %.3f s" % elapsed_time return delta
def benchmark(reference_system, positions, platform_name=None, nsteps=500, timestep=1.0*unit.femtoseconds, factory_args=None): """ Benchmark performance of alchemically modified system relative to original system. Parameters ---------- reference_system : simtk.openmm.System The reference System object to compare with positions : simtk.unit.Quantity with units compatible with nanometers The positions to assess energetics for. platform_name : str, optional, default=None The name of the platform to use for benchmarking. nsteps : int, optional, default=500 Number of molecular dynamics steps to use for benchmarking. timestep : simtk.unit.Quantity with units compatible with femtoseconds, optional, default=1*femtoseconds Timestep to use for benchmarking. factory_args : dict(), optional, default=None Arguments passed to AbsoluteAlchemicalFactory. """ from alchemy import AbsoluteAlchemicalFactory, AlchemicalState # Create a factory to produce alchemical intermediates. print("Creating alchemical factory...") initial_time = time.time() factory = AbsoluteAlchemicalFactory(reference_system, **factory_args) final_time = time.time() elapsed_time = final_time - initial_time print("AbsoluteAlchemicalFactory initialization took %.3f s" % elapsed_time) # Create an alchemically-perturbed state corresponding to nearly fully-interacting. # NOTE: We use a lambda slightly smaller than 1.0 because the AlchemicalFactory does not use Custom*Force softcore versions if lambda = 1.0 identically. lambda_value = 1.0 - 1.0e-6 alchemical_state = AlchemicalState(lambda_electrostatics=lambda_value, lambda_sterics=lambda_value, lambda_torsions=lambda_value) platform = None if platform_name: platform = openmm.Platform.getPlatformByName(platform_name) temperature = 300.*unit.kelvin collision_rate = 90./unit.picoseconds # Create the perturbed system. print("Creating alchemically-modified state...") initial_time = time.time() alchemical_system = factory.createPerturbedSystem(alchemical_state) final_time = time.time() elapsed_time = final_time - initial_time # Compare energies. print("Computing reference energies...") reference_integrator = openmm.LangevinIntegrator(temperature, collision_rate, timestep) if platform: reference_context = openmm.Context(reference_system, reference_integrator, platform) else: reference_context = openmm.Context(reference_system, reference_integrator) reference_context.setPositions(positions) reference_state = reference_context.getState(getEnergy=True) reference_potential = reference_state.getPotentialEnergy() print(reference_potential) print("Computing alchemical energies...") alchemical_integrator = openmm.LangevinIntegrator(temperature, collision_rate, timestep) if platform: alchemical_context = openmm.Context(alchemical_system, alchemical_integrator, platform) else: alchemical_context = openmm.Context(alchemical_system, alchemical_integrator) alchemical_context.setPositions(positions) alchemical_state = alchemical_context.getState(getEnergy=True) alchemical_potential = alchemical_state.getPotentialEnergy() print(reference_potential) # Make sure all kernels are compiled. reference_integrator.step(2) alchemical_integrator.step(2) # Time simulations. print("Simulating reference system...") initial_time = time.time() reference_integrator.step(nsteps) reference_state = reference_context.getState() #reference_state = reference_context.getState(getEnergy=True) #reference_potential = reference_state.getPotentialEnergy() final_time = time.time() reference_time = final_time - initial_time print("Simulating alchemical system...") initial_time = time.time() alchemical_integrator.step(nsteps) reference_state = alchemical_context.getState() #alchemical_state = alchemical_context.getState(getEnergy=True) #alchemical_potential = alchemical_state.getPotentialEnergy() final_time = time.time() alchemical_time = final_time - initial_time seconds_per_day = (1.*unit.day)/(1.*unit.seconds) print("TIMINGS") print("reference system : %12.3f s for %8d steps (%12.3f ms/step; %12.3f ns/day)" % (reference_time, nsteps, reference_time/nsteps*1000, nsteps*timestep*(seconds_per_day/reference_time)/unit.nanoseconds)) print("alchemical system : %12.3f s for %8d steps (%12.3f ms/step; %12.3f ns/day)" % (alchemical_time, nsteps, alchemical_time/nsteps*1000, nsteps*timestep*(seconds_per_day/alchemical_time)/unit.nanoseconds)) print("alchemical simulation is %12.3f x slower than unperturbed system" % (alchemical_time / reference_time))
def test_overlap(): """ BUGS TO REPORT: * Even if epsilon = 0, energy of two overlapping atoms is 'nan'. * Periodicity in 'nan' if dr = 0.1 even in nonperiodic system """ # Create a reference system. import testsystems print "Creating Lennard-Jones cluster system..." #[reference_system, coordinates] = testsystems.LennardJonesFluid() #receptor_atoms = [0] #ligand_atoms = [1] [reference_system, coordinates] = testsystems.LysozymeImplicit() receptor_atoms = range(0,2603) # T4 lysozyme L99A ligand_atoms = range(2603,2621) # p-xylene import simtk.unit as units unit = coordinates.unit coordinates = units.Quantity(numpy.array(coordinates / unit), unit) factory = AbsoluteAlchemicalFactory(reference_system, ligand_atoms=ligand_atoms) alchemical_state = AlchemicalState(0.00, 0.00, 0.00, 1.0) # Create the perturbed system. print "Creating alchemically-modified state..." alchemical_system = factory.createPerturbedSystem(alchemical_state) # Compare energies. import simtk.unit as units import simtk.openmm as openmm timestep = 1.0 * units.femtosecond print "Computing reference energies..." integrator = openmm.VerletIntegrator(timestep) context = openmm.Context(reference_system, integrator) context.setPositions(coordinates) state = context.getState(getEnergy=True) reference_potential = state.getPotentialEnergy() del state, context, integrator print reference_potential print "Computing alchemical energies..." integrator = openmm.VerletIntegrator(timestep) context = openmm.Context(alchemical_system, integrator) dr = 0.1 * units.angstroms # TODO: Why does 0.1 cause periodic 'nan's? a = receptor_atoms[-1] b = ligand_atoms[-1] delta = coordinates[a,:] - coordinates[b,:] for k in range(3): coordinates[ligand_atoms,k] += delta[k] for i in range(30): r = dr * i coordinates[ligand_atoms,0] += dr context.setPositions(coordinates) state = context.getState(getEnergy=True) alchemical_potential = state.getPotentialEnergy() print "%8.3f A : %f " % (r / units.angstroms, alchemical_potential / units.kilocalories_per_mole) del state, context, integrator return
def benchmark( reference_system, coordinates, receptor_atoms, ligand_atoms, platform_name="CUDA", annihilateElectrostatics=True, annihilateLennardJones=False, nsteps=500, ): """ Benchmark performance relative to unmodified system. ARGUMENTS reference_system (simtk.openmm.System) - the reference System object to compare with coordinates - the coordinates to assess energetics for receptor_atoms (list of int) - the list of receptor atoms ligand_atoms (list of int) - the list of ligand atoms to alchemically modify """ import simtk.unit as units import simtk.openmm as openmm import time # Create a factory to produce alchemical intermediates. print "Creating alchemical factory..." initial_time = time.time() factory = AbsoluteAlchemicalFactory(reference_system, ligand_atoms=ligand_atoms) final_time = time.time() elapsed_time = final_time - initial_time print "AbsoluteAlchemicalFactory initialization took %.3f s" % elapsed_time # Create an alchemically-perturbed state corresponding to nearly fully-interacting. # NOTE: We use a lambda slightly smaller than 1.0 because the AlchemicalFactory does not use Custom*Force softcore versions if lambda = 1.0 identically. lambda_value = 1.0 - 1.0e-6 alchemical_state = AlchemicalState(0.00, lambda_value, lambda_value, lambda_value) alchemical_state.annihilateElectrostatics = annihilateElectrostatics alchemical_state.annihilateLennardJones = annihilateLennardJones platform = openmm.Platform.getPlatformByName(platform_name) # Create the perturbed system. print "Creating alchemically-modified state..." initial_time = time.time() alchemical_system = factory.createPerturbedSystem(alchemical_state) final_time = time.time() elapsed_time = final_time - initial_time # Compare energies. timestep = 1.0 * units.femtosecond print "Computing reference energies..." reference_integrator = openmm.VerletIntegrator(timestep) reference_context = openmm.Context(reference_system, reference_integrator, platform) reference_context.setPositions(coordinates) reference_state = reference_context.getState(getEnergy=True) reference_potential = reference_state.getPotentialEnergy() print "Computing alchemical energies..." alchemical_integrator = openmm.VerletIntegrator(timestep) alchemical_context = openmm.Context(alchemical_system, alchemical_integrator, platform) alchemical_context.setPositions(coordinates) alchemical_state = alchemical_context.getState(getEnergy=True) alchemical_potential = alchemical_state.getPotentialEnergy() delta = alchemical_potential - reference_potential # Make sure all kernels are compiled. reference_integrator.step(1) alchemical_integrator.step(1) # Time simulations. import time print "Simulating reference system..." initial_time = time.time() reference_integrator.step(nsteps) reference_state = reference_context.getState(getEnergy=True) reference_potential = reference_state.getPotentialEnergy() final_time = time.time() reference_time = final_time - initial_time print "Simulating alchemical system..." initial_time = time.time() alchemical_integrator.step(nsteps) alchemical_state = alchemical_context.getState(getEnergy=True) alchemical_potential = alchemical_state.getPotentialEnergy() final_time = time.time() alchemical_time = final_time - initial_time print "TIMINGS" print "reference system : %12.3f s for %8d steps (%12.3f ms/step)" % ( reference_time, nsteps, reference_time / nsteps * 1000, ) print "alchemical system : %12.3f s for %8d steps (%12.3f ms/step)" % ( alchemical_time, nsteps, alchemical_time / nsteps * 1000, ) print "alchemical simulation is %12.3f x slower than unperturbed system" % (alchemical_time / reference_time) return delta
def overlap_check(reference_system, positions, platform_name=None, precision=None, nsteps=50, nsamples=200, factory_args=None, cached_trajectory_filename=None): """ Test overlap between reference system and alchemical system by running a short simulation. Parameters ---------- reference_system : simtk.openmm.System The reference System object to compare with positions : simtk.unit.Quantity with units compatible with nanometers The positions to assess energetics for. platform_name : str, optional, default=None The name of the platform to use for benchmarking. nsteps : int, optional, default=50 Number of molecular dynamics steps between samples. nsamples : int, optional, default=100 Number of samples to collect. factory_args : dict(), optional, default=None Arguments passed to AbsoluteAlchemicalFactory. cached_trajectory_filename : str, optional, default=None If specified, attempt to cache (or reuse) trajectory. """ # Create a fully-interacting alchemical state. factory = AbsoluteAlchemicalFactory(reference_system, **factory_args) alchemical_state = AlchemicalState() alchemical_system = factory.createPerturbedSystem(alchemical_state) temperature = 300.0 * unit.kelvin collision_rate = 5.0 / unit.picoseconds timestep = 2.0 * unit.femtoseconds kT = (kB * temperature) # Select platform. platform = None if platform_name: platform = openmm.Platform.getPlatformByName(platform_name) # Create integrators. reference_integrator = openmm.LangevinIntegrator(temperature, collision_rate, timestep) alchemical_integrator = openmm.VerletIntegrator(timestep) # Create contexts. if platform: reference_context = openmm.Context(reference_system, reference_integrator, platform) alchemical_context = openmm.Context(alchemical_system, alchemical_integrator, platform) else: reference_context = openmm.Context(reference_system, reference_integrator) alchemical_context = openmm.Context(alchemical_system, alchemical_integrator) ncfile = None if cached_trajectory_filename: cache_mode = 'write' # Try reading from cache from netCDF4 import Dataset if os.path.exists(cached_trajectory_filename): try: ncfile = Dataset(cached_trajectory_filename, 'r') if (ncfile.variables['positions'].shape == (nsamples, reference_system.getNumParticles(), 3)): # Read the cache if everything matches cache_mode = 'read' except: pass if cache_mode == 'write': # If anything went wrong, create a new cache. try: (pathname, filename) = os.path.split(cached_trajectory_filename) if not os.path.exists(pathname): os.makedirs(pathname) ncfile = Dataset(cached_trajectory_filename, 'w', format='NETCDF4') ncfile.createDimension('samples', 0) ncfile.createDimension('atoms', reference_system.getNumParticles()) ncfile.createDimension('spatial', 3) ncfile.createVariable('positions', 'f4', ('samples', 'atoms', 'spatial')) except Exception as e: logger.info(str(e)) logger.info('Could not create a trajectory cache (%s).' % cached_trajectory_filename) ncfile = None # Collect simulation data. reference_context.setPositions(positions) du_n = np.zeros([nsamples], np.float64) # du_n[n] is the print() import click with click.progressbar(range(nsamples)) as bar: for sample in bar: if cached_trajectory_filename and (cache_mode == 'read'): # Load cached frames. positions = unit.Quantity(ncfile.variables['positions'][sample,:,:], unit.nanometers) reference_context.setPositions(positions) else: # Run dynamics. reference_integrator.step(nsteps) # Get reference energies. reference_state = reference_context.getState(getEnergy=True, getPositions=True) reference_potential = reference_state.getPotentialEnergy() if np.isnan(reference_potential/kT): raise Exception("Reference potential is NaN") # Get alchemical energies. alchemical_context.setPositions(reference_state.getPositions(asNumpy=True)) alchemical_state = alchemical_context.getState(getEnergy=True) alchemical_potential = alchemical_state.getPotentialEnergy() if np.isnan(alchemical_potential/kT): raise Exception("Alchemical potential is NaN") du_n[sample] = (alchemical_potential - reference_potential) / kT if cached_trajectory_filename and (cache_mode == 'write') and (ncfile is not None): ncfile.variables['positions'][sample,:,:] = reference_state.getPositions(asNumpy=True) / unit.nanometers # Clean up. del reference_context, alchemical_context if cached_trajectory_filename and (ncfile is not None): ncfile.close() # Discard data to equilibration and subsample. from pymbar import timeseries [t0, g, Neff] = timeseries.detectEquilibration(du_n) indices = timeseries.subsampleCorrelatedData(du_n, g=g) du_n = du_n[indices] # Compute statistics. from pymbar import EXP [DeltaF, dDeltaF] = EXP(du_n) # Raise an exception if the error is larger than 3kT. MAX_DEVIATION = 3.0 # kT if (dDeltaF > MAX_DEVIATION): report = "DeltaF = %12.3f +- %12.3f kT (%5d samples, g = %6.1f)" % (DeltaF, dDeltaF, Neff, g) raise Exception(report) return
def overlap_check( reference_system, positions, receptor_atoms, ligand_atoms, platform_name=None, annihilate_electrostatics=True, annihilate_sterics=False, precision=None, nsteps=50, nsamples=200, ): """ Test overlap between reference system and alchemical system by running a short simulation. Parameters ---------- reference_system : simtk.openmm.System The reference System object to compare with positions : simtk.unit.Quantity with units compatible with nanometers The positions to assess energetics for. receptor_atoms : list of int The list of receptor atoms. ligand_atoms : list of int The list of ligand atoms to alchemically modify. platform_name : str, optional, default=None The name of the platform to use for benchmarking. annihilate_electrostatics : bool, optional, default=True If True, electrostatics will be annihilated; if False, decoupled. annihilate_sterics : bool, optional, default=False If True, sterics will be annihilated; if False, decoupled. nsteps : int, optional, default=50 Number of molecular dynamics steps between samples. nsamples : int, optional, default=100 Number of samples to collect. """ # Create a fully-interacting alchemical state. factory = AbsoluteAlchemicalFactory(reference_system, ligand_atoms=ligand_atoms) alchemical_state = AlchemicalState() alchemical_system = factory.createPerturbedSystem(alchemical_state) temperature = 300.0 * unit.kelvin collision_rate = 5.0 / unit.picoseconds timestep = 2.0 * unit.femtoseconds kT = kB * temperature # Select platform. platform = None if platform_name: platform = openmm.Platform.getPlatformByName(platform_name) # Create integrators. reference_integrator = openmm.LangevinIntegrator(temperature, collision_rate, timestep) alchemical_integrator = openmm.VerletIntegrator(timestep) # Create contexts. if platform: reference_context = openmm.Context(reference_system, reference_integrator, platform) alchemical_context = openmm.Context(alchemical_system, alchemical_integrator, platform) else: reference_context = openmm.Context(reference_system, reference_integrator) alchemical_context = openmm.Context(alchemical_system, alchemical_integrator) # Collect simulation data. reference_context.setPositions(positions) du_n = np.zeros([nsamples], np.float64) # du_n[n] is the for sample in range(nsamples): # Run dynamics. reference_integrator.step(nsteps) # Get reference energies. reference_state = reference_context.getState(getEnergy=True, getPositions=True) reference_potential = reference_state.getPotentialEnergy() # Get alchemical energies. alchemical_context.setPositions(reference_state.getPositions()) alchemical_state = alchemical_context.getState(getEnergy=True) alchemical_potential = alchemical_state.getPotentialEnergy() du_n[sample] = (alchemical_potential - reference_potential) / kT # Clean up. del reference_context, alchemical_context # Discard data to equilibration and subsample. from pymbar import timeseries [t0, g, Neff] = timeseries.detectEquilibration(du_n) indices = timeseries.subsampleCorrelatedData(du_n, g=g) du_n = du_n[indices] # Compute statistics. from pymbar import EXP [DeltaF, dDeltaF] = EXP(du_n) # Raise an exception if the error is larger than 3kT. MAX_DEVIATION = 3.0 # kT if dDeltaF > MAX_DEVIATION: report = "DeltaF = %12.3f +- %12.3f kT (%5d samples, g = %6.1f)" % (DeltaF, dDeltaF, Neff, g) raise Exception(report) return
def benchmark( reference_system, positions, receptor_atoms, ligand_atoms, platform_name=None, annihilate_electrostatics=True, annihilate_sterics=False, nsteps=500, timestep=1.0 * unit.femtoseconds, ): """ Benchmark performance of alchemically modified system relative to original system. Parameters ---------- reference_system : simtk.openmm.System The reference System object to compare with positions : simtk.unit.Quantity with units compatible with nanometers The positions to assess energetics for. receptor_atoms : list of int The list of receptor atoms. ligand_atoms : list of int The list of ligand atoms to alchemically modify. platform_name : str, optional, default=None The name of the platform to use for benchmarking. annihilate_electrostatics : bool, optional, default=True If True, electrostatics will be annihilated; if False, decoupled. annihilate_sterics : bool, optional, default=False If True, sterics will be annihilated; if False, decoupled. nsteps : int, optional, default=500 Number of molecular dynamics steps to use for benchmarking. timestep : simtk.unit.Quantity with units compatible with femtoseconds, optional, default=1*femtoseconds Timestep to use for benchmarking. """ # Create a factory to produce alchemical intermediates. logger.info("Creating alchemical factory...") initial_time = time.time() factory = AbsoluteAlchemicalFactory( reference_system, ligand_atoms=ligand_atoms, annihilate_electrostatics=annihilate_electrostatics, annihilate_sterics=annihilate_sterics, ) final_time = time.time() elapsed_time = final_time - initial_time logger.info("AbsoluteAlchemicalFactory initialization took %.3f s" % elapsed_time) # Create an alchemically-perturbed state corresponding to nearly fully-interacting. # NOTE: We use a lambda slightly smaller than 1.0 because the AlchemicalFactory does not use Custom*Force softcore versions if lambda = 1.0 identically. lambda_value = 1.0 - 1.0e-6 alchemical_state = AlchemicalState( lambda_coulomb=lambda_value, lambda_sterics=lambda_value, lambda_torsions=lambda_value ) platform = None if platform_name: platform = openmm.Platform.getPlatformByName(platform_name) # Create the perturbed system. logger.info("Creating alchemically-modified state...") initial_time = time.time() alchemical_system = factory.createPerturbedSystem(alchemical_state) final_time = time.time() elapsed_time = final_time - initial_time # Compare energies. logger.info("Computing reference energies...") reference_integrator = openmm.VerletIntegrator(timestep) if platform: reference_context = openmm.Context(reference_system, reference_integrator, platform) else: reference_context = openmm.Context(reference_system, reference_integrator) reference_context.setPositions(positions) reference_state = reference_context.getState(getEnergy=True) reference_potential = reference_state.getPotentialEnergy() logger.info("Computing alchemical energies...") alchemical_integrator = openmm.VerletIntegrator(timestep) if platform: alchemical_context = openmm.Context(alchemical_system, alchemical_integrator, platform) else: alchemical_context = openmm.Context(alchemical_system, alchemical_integrator) alchemical_context.setPositions(positions) alchemical_state = alchemical_context.getState(getEnergy=True) alchemical_potential = alchemical_state.getPotentialEnergy() delta = alchemical_potential - reference_potential # Make sure all kernels are compiled. reference_integrator.step(1) alchemical_integrator.step(1) # Time simulations. logger.info("Simulating reference system...") initial_time = time.time() reference_integrator.step(nsteps) reference_state = reference_context.getState(getEnergy=True) reference_potential = reference_state.getPotentialEnergy() final_time = time.time() reference_time = final_time - initial_time logger.info("Simulating alchemical system...") initial_time = time.time() alchemical_integrator.step(nsteps) alchemical_state = alchemical_context.getState(getEnergy=True) alchemical_potential = alchemical_state.getPotentialEnergy() final_time = time.time() alchemical_time = final_time - initial_time logger.info("TIMINGS") logger.info( "reference system : %12.3f s for %8d steps (%12.3f ms/step)" % (reference_time, nsteps, reference_time / nsteps * 1000) ) logger.info( "alchemical system : %12.3f s for %8d steps (%12.3f ms/step)" % (alchemical_time, nsteps, alchemical_time / nsteps * 1000) ) logger.info("alchemical simulation is %12.3f x slower than unperturbed system" % (alchemical_time / reference_time)) return delta