def alchemical_factory_check(reference_system, positions, platform_name=None, precision=None, factory_args=None): """ Compare energies of reference system and fully-interacting alchemically modified system. ARGUMENTS reference_system : simtk.openmm.System The reference System object to compare with positions : simtk.unit.Quantity of dimentsion [natoms,3] with units compatible with angstroms The positions to assess energetics for precision : str, optional, default=None Precision model, or default if not specified. ('single', 'double', 'mixed') factory_args : dict(), optional, default=None Arguments passed to AbsoluteAlchemicalFactory. """ # Create a factory to produce alchemical intermediates. logger.info("Creating alchemical factory...") initial_time = time.time() factory = AbsoluteAlchemicalFactory(reference_system, **factory_args) final_time = time.time() elapsed_time = final_time - initial_time logger.info("AbsoluteAlchemicalFactory initialization took %.3f s" % elapsed_time) platform = None if platform_name: platform = openmm.Platform.getPlatformByName(platform_name) alchemical_system = factory.createPerturbedSystem() compareSystemEnergies(positions, [reference_system, alchemical_system], ['reference', 'alchemical'], platform=platform, precision=precision) return
def compare_platforms(system, positions, factory_args=dict()): # Create annihilated version of vacuum system. factory = AbsoluteAlchemicalFactory(system, **factory_args) alchemical_system = factory.createPerturbedSystem() def set_lambda(alchemical_system, lambda_value): alchemical_state = AlchemicalState(lambda_electrostatics=lambda_value, lambda_sterics=lambda_value, lambda_torsions=lambda_value) AbsoluteAlchemicalFactory.perturbSystem(alchemical_system, alchemical_state) # Compare energies energies = dict() platform_names = list() for platform_index in range(openmm.Platform.getNumPlatforms()): platform = openmm.Platform.getPlatform(platform_index) platform_name = platform.getName() if platform_name != 'Reference': platform_names.append(platform_name) energies[platform_name] = dict() energies[platform_name]['full'] = compute_energy(system, positions, platform=platform) set_lambda(alchemical_system, 1.0) energies[platform_name]['lambda = 1'] = compute_energy(alchemical_system, positions, platform=platform) set_lambda(alchemical_system, 0.0) energies[platform_name]['lambda = 0'] = compute_energy(alchemical_system, positions, platform=platform) # Check deviations. for platform_name in platform_names: for energy_name in ['full', 'lambda = 1', 'lambda = 0']: delta = energies[platform_name][energy_name] - energies['Reference'][energy_name] if (abs(delta) > MAX_DELTA): raise Exception("Maximum allowable deviation on platform %s exceeded (was %.8f kcal/mol; allowed %.8f kcal/mol); test failed." % (platform_name, delta / unit.kilocalories_per_mole, MAX_DELTA / unit.kilocalories_per_mole))
def __init__(self, store_directory): """ Initialize YANK object with default parameters. Parameters ---------- store_directory : str The storage directory in which output NetCDF files are read or written. """ # Record that we are not yet initialized. self._initialized = False # Store output directory. self._store_directory = store_directory # Public attributes. self.restraint_type = 'flat-bottom' # default to a flat-bottom restraint between the ligand and receptor self.randomize_ligand = False self.randomize_ligand_sigma_multiplier = 2.0 self.randomize_ligand_close_cutoff = 1.5 * unit.angstrom # TODO: Allow this to be specified by user. self.mc_displacement_sigma = 10.0 * unit.angstroms # Set internal variables. self._phases = list() self._store_filenames = dict() # Default alchemical protocols. self.default_protocols = dict() self.default_protocols[ 'vacuum'] = AbsoluteAlchemicalFactory.defaultVacuumProtocol() self.default_protocols[ 'solvent-implicit'] = AbsoluteAlchemicalFactory.defaultSolventProtocolImplicit( ) self.default_protocols[ 'complex-implicit'] = AbsoluteAlchemicalFactory.defaultComplexProtocolImplicit( ) self.default_protocols[ 'solvent-explicit'] = AbsoluteAlchemicalFactory.defaultSolventProtocolExplicit( ) self.default_protocols[ 'complex-explicit'] = AbsoluteAlchemicalFactory.defaultComplexProtocolExplicit( ) # Default options for repex. self.default_options = dict() self.default_options['number_of_equilibration_iterations'] = 0 self.default_options['number_of_iterations'] = 100 self.default_options['number_of_iterations'] = 100 self.default_options['timestep'] = 2.0 * unit.femtoseconds self.default_options['collision_rate'] = 5.0 / unit.picoseconds self.default_options['minimize'] = False self.default_options[ 'show_mixing_statistics'] = True # this causes slowdown with iteration and should not be used for production self.default_options['platform'] = None self.default_options[ 'displacement_sigma'] = 1.0 * unit.nanometers # attempt to displace ligand by this stddev will be made each iteration return
def _minimize_replica(self, replica_index): """ Minimize the specified replica. """ # Create and cache Integrator and Context if needed. if not hasattr(self, '_context'): self._cache_context() context = self._context # Retrieve thermodynamic 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 # Set alchemical state. AbsoluteAlchemicalFactory.perturbContext(context, state.alchemical_state) # Set box vectors. box_vectors = self.replica_box_vectors[replica_index] context.setPeriodicBoxVectors(box_vectors[0, :], box_vectors[1, :], box_vectors[2, :]) # Set positions. positions = self.replica_positions[replica_index] context.setPositions(positions) # Report initial energy logger.debug( "Replica %5d/%5d: initial energy %8.3f kT", replica_index, self.nstates, state.reduced_potential(positions, box_vectors=box_vectors, context=context)) # Minimize energy. self.mm.LocalEnergyMinimizer.minimize(context, self.minimize_tolerance, self.minimize_max_iterations) # Store final positions positions = context.getState( getPositions=True, enforcePeriodicBox=state.system.usesPeriodicBoundaryConditions( )).getPositions(asNumpy=True) self.replica_positions[replica_index] = positions logger.debug( "Replica %5d/%5d: final energy %8.3f kT", replica_index, self.nstates, state.reduced_potential(positions, box_vectors=box_vectors, context=context)) return
def _compute_energies(self): """ Compute energies of all replicas at all states. TODO * We have to re-order Context initialization if we have variable box volume * Parallel implementation """ # Create and cache Integrator and Context if needed. if not hasattr(self, '_context'): self._cache_context() logger.debug("Computing energies...") start_time = time.time() # Retrieve context. context = self._context if self.mpicomm: # MPI version. # Compute energies for this node's share of states. for state_index in range(self.mpicomm.rank, self.nstates, self.mpicomm.size): # Set alchemical state. AbsoluteAlchemicalFactory.perturbContext(context, self.states[state_index].alchemical_state) for replica_index in range(self.nstates): self.u_kl[replica_index,state_index] = self.states[state_index].reduced_potential(self.replica_positions[replica_index], box_vectors=self.replica_box_vectors[replica_index], context=context) # Send final energies to all nodes. energies_gather = self.mpicomm.allgather(self.u_kl[:,self.mpicomm.rank:self.nstates:self.mpicomm.size]) for state_index in range(self.nstates): source = state_index % self.mpicomm.size # node with trajectory data index = state_index // self.mpicomm.size # index within trajectory batch self.u_kl[:,state_index] = energies_gather[source][:,index] else: # Serial version. for state_index in range(self.nstates): # Set alchemical state. AbsoluteAlchemicalFactory.perturbContext(context, self.states[state_index].alchemical_state) for replica_index in range(self.nstates): self.u_kl[replica_index,state_index] = self.states[state_index].reduced_potential(self.replica_positions[replica_index], box_vectors=self.replica_box_vectors[replica_index], context=context) end_time = time.time() elapsed_time = end_time - start_time time_per_energy= elapsed_time / float(self.nstates)**2 logger.debug("Time to compute all energies %.3f s (%.3f per energy calculation)." % (elapsed_time, time_per_energy)) return
def __init__(self, store_directory, verbose=False): """ Initialize YANK object with default parameters. Parameters ---------- store_directory : str The storage directory in which output NetCDF files are read or written. verbose : bool, optional, default=False If True, will turn on verbose output. """ # Record that we are not yet initialized. self._initialized = False # Store output directory. self._store_directory = store_directory # Public attributes. self.verbose = verbose self.restraint_type = 'flat-bottom' # default to a flat-bottom restraint between the ligand and receptor self.randomize_ligand = True self.randomize_ligand_sigma_multiplier = 2.0 self.randomize_ligand_close_cutoff = 1.5 * unit.angstrom # TODO: Allow this to be specified by user. self.mc_displacement_sigma = 10.0 * unit.angstroms # Set internal variables. self._phases = list() self._store_filenames = dict() # Default alchemical protocols. self.default_protocols = dict() self.default_protocols['vacuum'] = AbsoluteAlchemicalFactory.defaultVacuumProtocol() self.default_protocols['solvent-implicit'] = AbsoluteAlchemicalFactory.defaultSolventProtocolImplicit() self.default_protocols['complex-implicit'] = AbsoluteAlchemicalFactory.defaultComplexProtocolImplicit() self.default_protocols['solvent-explicit'] = AbsoluteAlchemicalFactory.defaultSolventProtocolExplicit() self.default_protocols['complex-explicit'] = AbsoluteAlchemicalFactory.defaultComplexProtocolExplicit() # Default options for repex. self.default_options = dict() self.default_options['number_of_equilibration_iterations'] = 0 self.default_options['number_of_iterations'] = 100 self.default_options['verbose'] = self.verbose self.default_options['timestep'] = 2.0 * unit.femtoseconds self.default_options['collision_rate'] = 5.0 / unit.picoseconds self.default_options['minimize'] = False self.default_options['show_mixing_statistics'] = True # this causes slowdown with iteration and should not be used for production self.default_options['platform_names'] = None self.default_options['displacement_sigma'] = 1.0 * unit.nanometers # attempt to displace ligand by this stddev will be made each iteration return
def test_softcore_parameters(): """ Testing alchemical slave functions """ alchemical_functions = { 'lambda_sterics' : 'lambda', 'lambda_electrostatics' : 'lambda', 'lambda_bonds' : 'lambda', 'lambda_angles' : 'lambda', 'lambda_torsions' : 'lambda' } name = 'Lennard-Jones fluid with dispersion correction' test_system = copy.deepcopy(test_systems[name]) reference_system = test_system['test'].system positions = test_system['test'].positions factory_args = test_system['factory_args'] factory_args.update({ 'softcore_alpha' : 1.0, 'softcore_beta' : 1.0, 'softcore_a' : 1.0, 'softcore_b' : 1.0, 'softcore_c' : 1.0, 'softcore_d' : 1.0, 'softcore_e' : 1.0, 'softcore_f' : 1.0 }) factory = AbsoluteAlchemicalFactory(reference_system, **factory_args) alchemical_system = factory.createPerturbedSystem() compareSystemEnergies(positions, [reference_system, alchemical_system], ['reference', 'alchemical'])
def alchemical_factory_check( reference_system, positions, receptor_atoms, ligand_atoms, platform_name=None, annihilate_electrostatics=True, annihilate_sterics=False, precision=None, ): """ Compare energies of reference system and fully-interacting alchemically modified system. ARGUMENTS reference_system (simtk.openmm.System) - the reference System object to compare with positions - 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 precision : str, optional, default=None Precision model, or default if not specified. ('single', 'double', 'mixed') """ # 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) platform = None if platform_name: platform = openmm.Platform.getPlatformByName(platform_name) alchemical_system = factory.createPerturbedSystem() compareSystemEnergies( positions, [reference_system, alchemical_system], ["reference", "alchemical"], platform=platform, precision=precision, ) return
def make_alchemical_system(self, topology_proposal, direction='insert'): """ Generate an alchemically-modified system at the correct atoms based on the topology proposal Arguments --------- topology_proposal : TopologyProposal namedtuple Contains old topology, proposed new topology, and atom mapping direction : str, optional, default='insert' Direction of topology proposal to use for identifying alchemical atoms (allowed values: ['insert', 'delete']) Returns ------- unmodified_system : simtk.openmm.System Unmodified real system corresponding to appropriate leg of transformation. alchemical_system : simtk.openmm.System The system with appropriate atoms alchemically modified """ if direction not in ['insert', 'delete']: raise Exception("'direction' must be one of ['insert', 'delete']; was '%s' instead" % direction) atom_map = topology_proposal.new_to_old_atom_map #take the unique atoms as those not in the {new_atom : old_atom} atom map if direction == 'delete': unmodified_system = topology_proposal.old_system alchemical_atoms = [atom for atom in range(unmodified_system.getNumParticles()) if atom not in atom_map.values()] elif direction == 'insert': unmodified_system = topology_proposal.new_system alchemical_atoms = [atom for atom in range(unmodified_system.getNumParticles()) if atom not in atom_map.keys()] else: raise Exception("direction must be one of ['delete', 'insert']; found '%s' instead" % direction) # DEBUG #print('alchemical atoms:') #print(alchemical_atoms) # Create an alchemical factory. from alchemy import AbsoluteAlchemicalFactory alchemical_factory = AbsoluteAlchemicalFactory(unmodified_system, ligand_atoms=alchemical_atoms, annihilate_electrostatics=True, annihilate_sterics=True, alchemical_bonds=None, alchemical_angles=None) # Return the alchemically-modified system in fully-interacting form. alchemical_system = alchemical_factory.createPerturbedSystem() return [unmodified_system, alchemical_system]
def test_ncmc_alchemical_integrator_stability_molecules(): """ Test NCMCAlchemicalIntegrator """ molecule_names = ['pentane', 'biphenyl', 'imatinib'] #if os.environ.get("TRAVIS", None) == 'true': # molecule_names = ['pentane'] for molecule_name in molecule_names: from perses.tests.utils import createSystemFromIUPAC [molecule, system, positions, topology] = createSystemFromIUPAC(molecule_name) # Eliminate half of the molecule # TODO: Use a more rigorous scheme to make sure we are really cutting the molecule in half and not just eliminating hydrogens or something. alchemical_atoms = [ index for index in range(int(system.getNumParticles()/2)) ] # Create an alchemically-modified system. from alchemy import AbsoluteAlchemicalFactory alchemical_factory = AbsoluteAlchemicalFactory(system, ligand_atoms=alchemical_atoms, annihilate_electrostatics=True, annihilate_sterics=True) # Return the alchemically-modified system in fully-interacting form. alchemical_system = alchemical_factory.createPerturbedSystem() # Create an NCMC switching integrator. from perses.annihilation.ncmc_switching import NCMCVVAlchemicalIntegrator temperature = 300.0 * unit.kelvin nsteps = 10 # number of steps to run integration for functions = { 'lambda_sterics' : 'lambda', 'lambda_electrostatics' : 'lambda^0.5', 'lambda_torsions' : 'lambda', 'lambda_angles' : 'lambda^2' } ncmc_integrator = NCMCVVAlchemicalIntegrator(temperature, alchemical_system, functions, direction='delete', nsteps=nsteps, timestep=1.0*unit.femtoseconds) # Create a Context context = openmm.Context(alchemical_system, ncmc_integrator) context.setPositions(positions) # Run the integrator ncmc_integrator.step(nsteps) # Check positions are finite positions = context.getState(getPositions=True).getPositions(asNumpy=True) if np.isnan(np.any(positions / positions.unit)): raise Exception('NCMCAlchemicalIntegrator gave NaN positions') if np.isnan(ncmc_integrator.getLogAcceptanceProbability(context)): raise Exception('NCMCAlchemicalIntegrator gave NaN logAcceptanceProbability') del context, ncmc_integrator
def check_waterbox(platform=None, precision=None, nonbondedMethod=openmm.NonbondedForce.CutoffPeriodic): """Compare annihilated states in vacuum and a large box. """ platform_name = platform.getName() from openmmtools import testsystems testsystem = testsystems.WaterBox() system = testsystem.system positions = testsystem.positions # Use reaction field for force in system.getForces(): if force.__class__.__name__ == 'NonbondedForce': force.setNonbondedMethod(nonbondedMethod) factory_args = {'ligand_atoms' : [], 'receptor_atoms' : [], 'annihilate_sterics' : False, 'annihilate_electrostatics' : True } # Create alchemically-modified system factory = AbsoluteAlchemicalFactory(system, **factory_args) alchemical_system = factory.createPerturbedSystem() # Compare energies system_energy = compute_energy(system, positions, platform=platform, precision=precision) alchemical_1_energy = compute_energy(alchemical_system, positions, platform=platform, precision=precision) # Set lambda = 0 lambda_value = 0.0 alchemical_state = AlchemicalState(lambda_electrostatics=lambda_value, lambda_sterics=lambda_value, lambda_torsions=lambda_value) AbsoluteAlchemicalFactory.perturbSystem(alchemical_system, alchemical_state) alchemical_0_energy = compute_energy(alchemical_system, positions, platform=platform, precision=precision) # Check deviation. logger.info("========") logger.info("Platform %s" % platform_name) logger.info("Alchemically-modified WaterBox with no alchemical atoms") logger.info('real system : %8.3f kcal/mol' % (system_energy / unit.kilocalories_per_mole)) logger.info('lambda = 1 : %8.3f kcal/mol' % (alchemical_1_energy / unit.kilocalories_per_mole)) logger.info('lambda = 0 : %8.3f kcal/mol' % (alchemical_0_energy / unit.kilocalories_per_mole)) delta = alchemical_1_energy - alchemical_0_energy logger.info("ERROR : %8.3f kcal/mol" % (delta / unit.kilocalories_per_mole)) if (abs(delta) > MAX_DELTA): raise Exception("Maximum allowable deviation on platform %s exceeded (was %.8f kcal/mol; allowed %.8f kcal/mol); test failed." % (platform_name, delta / unit.kilocalories_per_mole, MAX_DELTA / unit.kilocalories_per_mole))
def _minimize_replica(self, replica_index): """ Minimize the specified replica. """ # Create and cache Integrator and Context if needed. if not hasattr(self, '_context'): self._cache_context() context = self._context # Retrieve thermodynamic 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 # Set alchemical state. AbsoluteAlchemicalFactory.perturbContext(context, state.alchemical_state) # Set box vectors. box_vectors = self.replica_box_vectors[replica_index] context.setPeriodicBoxVectors(box_vectors[0,:], box_vectors[1,:], box_vectors[2,:]) # Set positions. positions = self.replica_positions[replica_index] context.setPositions(positions) # Report initial energy logger.debug("Replica %5d/%5d: initial energy %8.3f kT", replica_index, self.nstates, state.reduced_potential(positions, box_vectors=box_vectors, context=context)) # Minimize energy. self.mm.LocalEnergyMinimizer.minimize(context, self.minimize_tolerance, self.minimize_max_iterations) # Store final positions positions = context.getState(getPositions=True, enforcePeriodicBox=state.system.usesPeriodicBoundaryConditions()).getPositions(asNumpy=True) self.replica_positions[replica_index] = positions logger.debug("Replica %5d/%5d: final energy %8.3f kT", replica_index, self.nstates, state.reduced_potential(positions, box_vectors=box_vectors, context=context)) return
def make_alchemical_system(self, unmodified_system, topology_proposal, direction='create'): """ Generate an alchemically-modified system at the correct atoms based on the topology proposal Arguments --------- unmodified_system : openmm.System object The unmodified system to get alchemical modifications topology_proposal : TopologyProposal namedtuple Contains old topology, proposed new topology, and atom mapping direction : str, optional, default='insert' Direction of topology proposal to use for identifying alchemical atoms. Returns ------- alchemical_system : openmm.System object The system with appropriate atoms alchemically modified """ atom_map = topology_proposal.new_to_old_atom_map n_atoms = unmodified_system.getNumParticles() #take the unique atoms as those not in the {new_atom : old_atom} atom map if direction == 'delete': alchemical_atoms = [atom for atom in range(n_atoms) if atom not in atom_map.values()] elif direction == 'create': alchemical_atoms = [atom for atom in range(n_atoms) if atom not in atom_map.keys()] else: raise Exception("direction must be one of ['delete', 'create']; found '%s' instead" % direction) logging.debug(alchemical_atoms) # Create an alchemical factory. from alchemy import AbsoluteAlchemicalFactory alchemical_factory = AbsoluteAlchemicalFactory(unmodified_system, ligand_atoms=alchemical_atoms) # Return the alchemically-modified system. alchemical_system = alchemical_factory.createPerturbedSystem() return alchemical_system
def __init__(self, **kwargs): super(LoopSoftening, self).__init__(**kwargs) self.description = 'Alchemical Loop Softening script' padding = 9.0*unit.angstrom explicit_solvent_model = 'tip3p' setup_path = 'data/mtor' # Create topology, positions, and system. from pkg_resources import resource_filename gaff_xml_filename = resource_filename('sams', 'data/gaff.xml') system_generators = dict() ffxmls = [gaff_xml_filename, 'amber99sbildn.xml', 'tip3p.xml'] forcefield_kwargs={ 'nonbondedMethod' : app.CutoffPeriodic, 'nonbondedCutoff' : 9.0 * unit.angstrom, 'implicitSolvent' : None, 'constraints' : app.HBonds, 'rigidWater' : True } # Load topologies and positions for all components print('Creating mTOR test system...') forcefield = app.ForceField(*ffxmls) from simtk.openmm.app import PDBFile, Modeller pdb_filename = resource_filename('sams', os.path.join(setup_path, 'mtor_pdbfixer_apo.pdb')) pdbfile = PDBFile(pdb_filename) modeller = app.Modeller(pdbfile.topology, pdbfile.positions) print('Adding solvent...') modeller.addSolvent(forcefield, model=explicit_solvent_model, padding=padding) self.topology = modeller.getTopology() self.positions = modeller.getPositions() print('Creating system...') self.system = forcefield.createSystem(self.topology, **forcefield_kwargs) # DEBUG: Write PDB outfile = open('initial.pdb', 'w') PDBFile.writeFile(self.topology, self.positions, outfile) outfile.close() # Atom Selection using MDtraj res_pairs = [[403, 483], [1052, 1109]] t = md.load(pdb_filename) alchemical_atoms = [] for x in res_pairs: start = min(t.top.select('residue %s' % min(x))) end = max(t.top.select('residue %s' % max(x))) + 1 alchemical_atoms.append(list(range(start, end))) #print(alchemical_atoms) # Add a MonteCarloBarostat #temperature = 300 * unit.kelvin # will be replaced as thermodynamic state is updated #pressure = 1.0 * unit.atmospheres #barostat = openmm.MonteCarloBarostat(pressure, temperature) #self.system.addForce(barostat) # Create thermodynamic states. print('Creating alchemically-modified system...') temperature = 300 * unit.kelvin pressure = 1.0 * unit.atmospheres alchemical_atoms = range(0,69) # Abl:imatinib from alchemy import AbsoluteAlchemicalFactory factory = AbsoluteAlchemicalFactory(self.system, ligand_atoms=alchemical_atoms, annihilate_electrostatics=True, annihilate_sterics=False, softcore_beta=0.0) # turn off softcore electrostatics self.system = factory.createPerturbedSystem() print('Setting up alchemical intermediates...') from sams import ThermodynamicState self.thermodynamic_states = list() for state in range(251): parameters = {'lambda_sterics' : 1.0, 'lambda_electrostatics' : (1.0 - float(state)/250.0) } self.thermodynamic_states.append( ThermodynamicState(system=self.system, temperature=temperature, parameters=parameters) ) for state in range(1,251): parameters = {'lambda_sterics' : (1.0 - float(state)/250.0), 'lambda_electrostatics' : 0.0 } self.thermodynamic_states.append( ThermodynamicState(system=self.system, temperature=temperature, parameters=parameters) ) # Create SAMS samplers print('Setting up 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.verbose = True self.exen_sampler = ExpandedEnsembleSampler(self.mcmc_sampler, self.thermodynamic_states) self.exen_sampler.verbose = True self.sams_sampler = SAMSSampler(self.exen_sampler) self.sams_sampler.verbose = True
def _create_phase(self, phase, reference_system, positions, atom_indices, thermodynamic_state, protocols=None, options=None, mpicomm=None): """ Create a repex object for a specified phase. Parameters ---------- phase : str The phase being initialized (one of ['complex', 'solvent', 'vacuum']) reference_system : simtk.openmm.System The reference system object from which alchemical intermediates are to be construcfted. positions : list of simtk.unit.Qunatity objects containing (natoms x 3) positions (as np or lists) The list of positions to be used to seed replicas in a round-robin way. atom_indices : dict atom_indices[phase][component] is the set of atom indices associated with component, where component is ['ligand', 'receptor'] thermodynamic_state : ThermodynamicState Thermodynamic state from which reference temperature and pressure are to be taken. protocols : dict of list of AlchemicalState, optional, default=None If specified, the alchemical protocol protocols[phase] will be used for phase 'phase' instead of the default. options : dict of str, optional, default=None If specified, these options will override default repex simulation options. """ # Make sure positions argument is a list of coordinate snapshots. if hasattr(positions, 'unit'): # Wrap in list. positions = [positions] # Check the dimensions of positions. for index in range(len(positions)): # Make sure it is recast as a np array. positions[index] = unit.Quantity( np.array(positions[index] / positions[index].unit), positions[index].unit) [natoms, ndim] = (positions[index] / positions[index].unit).shape if natoms != reference_system.getNumParticles(): raise Exception( "positions argument must be a list of simtk.unit.Quantity of (natoms,3) lists or np array with units compatible with nanometers." ) # Create metadata storage. metadata = dict() # Make a deep copy of the reference system so we don't accidentally modify it. reference_system = copy.deepcopy(reference_system) # TODO: Use more general approach to determine whether system is periodic. is_periodic = self._is_periodic(reference_system) # Make sure pressure is None if not periodic. if not is_periodic: thermodynamic_state.pressure = None # Compute standard state corrections for complex phase. metadata['standard_state_correction'] = 0.0 # TODO: Do we need to include a standard state correction for other phases in periodic boxes? if phase == 'complex-implicit': # Impose restraints for complex system in implicit solvent to keep ligand from drifting too far away from receptor. if self.verbose: print "Creating receptor-ligand restraints..." reference_positions = positions[0] if self.restraint_type == 'harmonic': restraints = HarmonicReceptorLigandRestraint( thermodynamic_state, reference_system, reference_positions, atom_indices['receptor'], atom_indices['ligand']) elif self.restraint_type == 'flat-bottom': restraints = FlatBottomReceptorLigandRestraint( thermodynamic_state, reference_system, reference_positions, atom_indices['receptor'], atom_indices['ligand']) else: raise Exception("restraint_type of '%s' is not supported." % self.restraint_type) force = restraints.getRestraintForce( ) # Get Force object incorporating restraints reference_system.addForce(force) metadata[ 'standard_state_correction'] = restraints.getStandardStateCorrection( ) # standard state correction in kT elif phase == 'complex-explicit': # For periodic systems, we do not use a restraint, but must add a standard state correction for the box volume. # TODO: What if the box volume fluctuates during the simulation? box_vectors = reference_system.getDefaultPeriodicBoxVectors() box_volume = thermodynamic_state._volume(box_vectors) STANDARD_STATE_VOLUME = 1660.53928 * unit.angstrom**3 metadata['standard_state_correction'] = np.log( STANDARD_STATE_VOLUME / box_volume) # TODO: Check sign. # Use default alchemical protocols if not specified. if not protocols: protocols = self.default_protocols # Create alchemically-modified states using alchemical factory. if self.verbose: print "Creating alchemically-modified states..." factory = AbsoluteAlchemicalFactory( reference_system, ligand_atoms=atom_indices['ligand']) systems = factory.createPerturbedSystems(protocols[phase]) # Randomize ligand position if requested, but only for implicit solvent systems. if self.randomize_ligand and (phase == 'complex-implicit'): if self.verbose: print "Randomizing ligand positions and excluding overlapping configurations..." randomized_positions = list() nstates = len(systems) for state_index in range(nstates): positions_index = np.random.randint(0, len(positions)) current_positions = positions[positions_index] new_positions = ModifiedHamiltonianExchange.randomize_ligand_position( current_positions, atom_indices['receptor'], atom_indices['ligand'], self.randomize_ligand_sigma_multiplier * restraints.getReceptorRadiusOfGyration(), self.randomize_ligand_close_cutoff) randomized_positions.append(new_positions) positions = randomized_positions # Identify whether any atoms will be displaced via MC. mc_atoms = list() if 'ligand' in atom_indices: mc_atoms = atom_indices['ligand'] # Combine simulation options with defaults. options = dict(self.default_options.items() + options.items()) # Set up simulation. # TODO: Support MPI initialization? if self.verbose: print "Creating replica exchange object..." store_filename = os.path.join(self._store_directory, phase + '.nc') self._store_filenames[phase] = store_filename simulation = ModifiedHamiltonianExchange(store_filename, mpicomm=mpicomm) simulation.create(thermodynamic_state, systems, positions, displacement_sigma=self.mc_displacement_sigma, mc_atoms=mc_atoms, options=options, metadata=metadata) simulation.verbose = self.verbose # Initialize simulation. # TODO: Use the right scheme for initializing the simulation without running. #if self.verbose: print "Initializing simulation..." #simulation.run(0) # TODO: Process user-supplied options. # Clean up simulation. del simulation return
def setup_binding_amber(args): """ Set up ligand binding free energy calculation using AMBER prmtop/inpcrd files. Parameters ---------- args : dict Command-line arguments dict from docopt. Returns ------- alchemical_phases : list of AlchemicalPhase Phases (thermodynamic legs) of the calculation. """ verbose = args['--verbose'] setup_directory = args['--setupdir'] # Directory where prmtop/inpcrd files are to be found system_parameters = {} # parameters to pass to prmtop.createSystem # Implicit solvent if args['--gbsa']: system_parameters['implicitSolvent'] = getattr(app, args['--gbsa']) # Select nonbonded treatment if args['--nbmethod']: system_parameters['nonbondedMethod'] = getattr(app, args['--nbmethod']) # Constraints if args['--constraints']: system_parameters['constraints'] = getattr(app, args['--constraints']) # Cutoff if args['--cutoff']: system_parameters['nonbondedCutoff'] = process_unit_bearing_arg(args, '--cutoff', unit.nanometers) # Determine if this will be an explicit or implicit solvent simulation if ('nonbondedMethod' in system_parameters and system_parameters['nonbondedMethod'] != app.NoCutoff): phases_names = ['complex-explicit', 'solvent-explicit'] protocols = [AbsoluteAlchemicalFactory.defaultComplexProtocolExplicit(), AbsoluteAlchemicalFactory.defaultSolventProtocolExplicit()] else: phases_names = ['complex-implicit', 'solvent-implicit'] protocols = [AbsoluteAlchemicalFactory.defaultComplexProtocolImplicit(), AbsoluteAlchemicalFactory.defaultSolventProtocolImplicit()] # Prepare Yank arguments alchemical_phases = [None, None] setup_directory = os.path.join(setup_directory, '') # add final slash character system_files_paths = [[setup_directory + 'complex.inpcrd', setup_directory + 'complex.prmtop'], [setup_directory + 'solvent.inpcrd', setup_directory + 'solvent.prmtop']] for i, phase_name in enumerate(phases_names): positions_file_path = system_files_paths[i][0] topology_file_path = system_files_paths[i][1] logger.info("Reading phase {}".format(phase_name)) alchemical_phases[i] = pipeline.prepare_phase(positions_file_path, topology_file_path, args['--ligand'], system_parameters, verbose=verbose) alchemical_phases[i].name = phase_name alchemical_phases[i].protocol = protocols[i] return alchemical_phases
def _propagate_replica(self, replica_index): """ Attempt a Monte Carlo rotation/translation move followed by dynamics. """ # Create and cache Integrator and Context if needed. if not hasattr(self, '_context'): self._cache_context() # 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 cached integrator and context. integrator = self._integrator context = self._context # Set thermodynamic parameters for this state. integrator.setTemperature(state.temperature) integrator.setRandomNumberSeed(int(np.random.randint(0, MAX_SEED))) 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) context.setParameter(barostat.Pressure(), state.pressure) # must be set in context barostat.setRandomNumberSeed(int(np.random.randint(0, MAX_SEED))) # Set alchemical state. AbsoluteAlchemicalFactory.perturbContext(context, state.alchemical_state) # # Attempt a Monte Carlo rotation/translation move. # # 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, context=context) # 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, context=context) # Accept or reject with Metropolis criteria. du = u_new - u_old if (du <= 0.0) or (np.random.rand() < np.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, context=context) # Compute new potential. perturbed_positions = self.propose_rotation(original_positions, self.mc_atoms) u_new = state.reduced_potential(perturbed_positions, context=context) du = u_new - u_old if (du <= 0.0) or (np.random.rand() < np.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 dynamics. # start_time = time.time() # Set box vectors. box_vectors = self.replica_box_vectors[replica_index] context.setPeriodicBoxVectors(box_vectors[0,:], box_vectors[1,:], box_vectors[2,:]) # Set positions. positions = self.replica_positions[replica_index] context.setPositions(positions) setpositions_end_time = time.time() # Assign Maxwell-Boltzmann velocities. context.setVelocitiesToTemperature(state.temperature, int(np.random.randint(0, MAX_SEED))) setvelocities_end_time = time.time() # Run dynamics. integrator.step(self.nsteps_per_iteration) integrator_end_time = time.time() # Store final positions getstate_start_time = time.time() openmm_state = context.getState(getPositions=True,enforcePeriodicBox=True) getstate_end_time = time.time() self.replica_positions[replica_index] = openmm_state.getPositions(asNumpy=True) # Store box vectors. self.replica_box_vectors[replica_index] = openmm_state.getPeriodicBoxVectors(asNumpy=True) # Compute timing. end_time = time.time() elapsed_time = end_time - start_time positions_elapsed_time = setpositions_end_time - start_time velocities_elapsed_time = setvelocities_end_time - setpositions_end_time integrator_elapsed_time = integrator_end_time - setvelocities_end_time getstate_elapsed_time = getstate_end_time - integrator_end_time logger.debug("Replica %d/%d: integrator elapsed time %.3f s (positions %.3f s | velocities %.3f s | integrate+getstate %.3f s)." % (replica_index, self.nreplicas, elapsed_time, positions_elapsed_time, velocities_elapsed_time, integrator_elapsed_time+getstate_elapsed_time)) return elapsed_time
def _AutoAlchemyStates(self, phase, real_R_states=None, real_A_states=None, real_E_states=None, real_C_states=None, alchemy_source=None): #Generate the real alchemical states automatically. if alchemy_source: #Load alchemy from an external source import imp if alchemy_source[ -3:] != '.py': #Check if the file or the folder was provided alchemy_source = os.path.join(alchemy_source, 'alchemy.py') alchemy = imp.load_source('alchemy', alchemy_source) AAF = alchemy.AbsoluteAlchemicalFactory else: #Standard load from alchemy import AbsoluteAlchemicalFactory as AAF if phase is 'vacuum': protocol = AAF.defaultVacuumProtocol() elif phase is 'complex': protocol = AAF.defaultComplexProtocolExplicit() #Determine which phases need crunched if real_R_states is None: real_R_states = list() crunchR = True else: crunchR = False if real_A_states is None: real_A_states = list() crunchA = True else: crunchA = False if real_E_states is None: real_E_states = list() real_PMEFull_states = list() crunchE = True else: crunchE = False #Detect for the cap basis property if numpy.all([hasattr(state, 'ligandCapToFull') for state in protocol]) and real_C_states is None: real_C_states = list() crunchC = True else: crunchC = False #Import from the alchemy file if need be for state in protocol: #Go through each state if crunchE: real_E_states.append(state.ligandElectrostatics) try: real_PMEFull_states.append(state.ligandPMEFull) except: real_PMEFull_states.append(None) if crunchR: real_R_states.append(state.ligandRepulsion) if crunchA: real_A_states.append(state.ligandAttraction) if crunchC: real_C_states.append(state.ligandCapToFull) if numpy.all( [i is None for i in real_PMEFull_states] ): #Must put [...] around otherwise it creates the generator object which numpy.all evals to True self.PME_isolated = False else: self.PME_isolated = True #Determine cutoffs self.real_E_states = numpy.array(real_E_states) self.real_PMEFull_states = numpy.array(real_PMEFull_states) self.real_R_states = numpy.array(real_R_states) self.real_A_states = numpy.array(real_A_states) self.real_C_states = numpy.array(real_C_states) indicies = numpy.array(range(len(real_E_states))) #Determine Inversion if numpy.any(self.real_E_states < 0) or numpy.any( numpy.logical_and( self.real_PMEFull_states < 0, numpy.array( [i is not None for i in self.real_PMEFull_states]))): self.Inversion = True else: self.Inversion = False #Set the indicies, trap TypeError (logical_and false everywhere) as None (i.e. state not found in alchemy) if crunchC: #Check for the cap potential print "Not Coded Yet!" exit(1) #Create the Combinations basisVars = ["E", "A", "R", "C"] mappedStates = [ self.real_E_states, self.real_R_states, self.real_C_states, self.real_A_states ] nBasis = len(basisVars) coupled_states = {} decoupled_states = {} for iBasis in xrange(nBasis): coupled_states[basisVars[iBasis]] = numpy.where( mappedStates[iBasis] == 1.00)[ 0] #need the [0] to extract the array from the basis decoupled_states[basisVars[iBasis]] = numpy.where( mappedStates[iBasis] == 0.00)[0] self.coupled_states = coupled_states self.decoupled_states = decoupled_states self.basisVars = basisVars else: if self.PME_isolated: #Logic to solve for isolated PME case try: #Figure out the Fully coupled state self.real_EAR = int(indicies[numpy.logical_and( numpy.logical_and(self.real_E_states == 1, self.real_PMEFull_states == 1), numpy.logical_and(self.real_R_states == 1, self.real_A_states == 1))]) except TypeError: self.real_EAR = None try: self.real_AR = int(indicies[numpy.logical_and( numpy.logical_and(self.real_E_states == 0, self.real_PMEFull_states == 0), numpy.logical_and(self.real_R_states == 1, self.real_A_states == 1))]) except TypeError: self.real_AR = None try: self.real_R = int(indicies[numpy.logical_and( numpy.logical_and(self.real_E_states == 0, self.real_PMEFull_states == 0), numpy.logical_and(self.real_R_states == 1, self.real_A_states == 0))]) except TypeError: self.real_R = None try: self.real_alloff = int(indicies[numpy.logical_and( numpy.logical_and(self.real_E_states == 0, self.real_PMEFull_states == 0), numpy.logical_and(self.real_R_states == 0, self.real_A_states == 0))]) except: self.real_alloff = None try: self.real_PMEAR = int(indicies[numpy.logical_and( numpy.logical_and(self.real_E_states == 0, self.real_PMEFull_states == 1), numpy.logical_and(self.real_R_states == 1, self.real_A_states == 1))]) except TypeError: self.real_PMEAR = None try: self.real_PMEsolve = int(indicies[numpy.logical_and( numpy.logical_and( self.real_E_states == 0, numpy.logical_and(self.real_PMEFull_states != 1, self.real_PMEFull_states != 0)), numpy.logical_and(self.real_R_states == 1, self.real_A_states == 1))]) except TypeError: self.real_PMEsolve = None if self.Inversion: self.real_inverse = int(indicies[numpy.logical_and( numpy.logical_and( numpy.logical_and(self.real_E_states == -1, self.real_PMEFull_states == -1), self.real_R_states == 1), self.real_A_states == 1)]) else: try: self.real_EAR = int(indicies[numpy.logical_and( self.real_E_states == 1, numpy.logical_and(self.real_R_states == 1, self.real_A_states == 1))]) except TypeError: self.real_EAR = None try: self.real_AR = int(indicies[numpy.logical_and( self.real_E_states == 0, numpy.logical_and(self.real_R_states == 1, self.real_A_states == 1))]) except TypeError: self.real_AR = None try: self.real_R = int(indicies[numpy.logical_and( self.real_E_states == 0, numpy.logical_and(self.real_R_states == 1, self.real_A_states == 0))]) except TypeError: self.real_R = None try: self.real_alloff = int(indicies[numpy.logical_and( self.real_E_states == 0, numpy.logical_and(self.real_R_states == 0, self.real_A_states == 0))]) except: self.real_alloff = None if self.Inversion: self.real_inverse = int(indicies[numpy.logical_and( numpy.logical_and(self.real_E_states == -1, self.real_R_states == 1), self.real_A_states == 1)]) #Now that all the sorting and variable assignment has been done, must set the PME states which were not defined to the electrostatic state as thats how its coded (helps sorting algorithm later) #This algorighm also ensures that real_PMEFull_states is not dtype=object nstates = len(self.real_E_states) tempPME = numpy.zeros(nstates) for i in xrange(nstates): if self.real_PMEFull_states[i] is None: #Find where they are none tempPME[i] = self.real_E_states[ i] #Assign them equal to the E state else: tempPME[i] = self.real_PMEFull_states[i] self.real_PMEFull_states = tempPME return
def _AutoAlchemyStates(self, phase, real_R_states=None, real_A_states=None, real_E_states=None, real_C_states=None, alchemy_source=None): #Generate the real alchemical states automatically. if alchemy_source: #Load alchemy from an external source import imp if alchemy_source[-3:] != '.py': #Check if the file or the folder was provided alchemy_source = os.path.join(alchemy_source, 'alchemy.py') alchemy = imp.load_source('alchemy', alchemy_source) AAF = alchemy.AbsoluteAlchemicalFactory else: #Standard load from alchemy import AbsoluteAlchemicalFactory as AAF if phase is 'vacuum': protocol = AAF.defaultVacuumProtocol() elif phase is 'complex': protocol = AAF.defaultComplexProtocolExplicit() #Determine which phases need crunched if real_R_states is None: real_R_states = list() crunchR = True else: crunchR = False if real_A_states is None: real_A_states = list() crunchA = True else: crunchA = False if real_E_states is None: real_E_states = list() real_PMEFull_states = list() crunchE = True else: crunchE = False #Detect for the cap basis property if numpy.all([hasattr(state, 'ligandCapToFull') for state in protocol]) and real_C_states is None: real_C_states = list() crunchC = True else: crunchC = False #Import from the alchemy file if need be for state in protocol: #Go through each state if crunchE: real_E_states.append(state.ligandElectrostatics) try: real_PMEFull_states.append(state.ligandPMEFull) except: real_PMEFull_states.append(None) if crunchR: real_R_states.append(state.ligandRepulsion) if crunchA: real_A_states.append(state.ligandAttraction) if crunchC: real_C_states.append(state.ligandCapToFull) if numpy.all([i is None for i in real_PMEFull_states]): #Must put [...] around otherwise it creates the generator object which numpy.all evals to True self.PME_isolated = False else: self.PME_isolated = True #Determine cutoffs self.real_E_states = numpy.array(real_E_states) self.real_PMEFull_states = numpy.array(real_PMEFull_states) self.real_R_states = numpy.array(real_R_states) self.real_A_states = numpy.array(real_A_states) self.real_C_states = numpy.array(real_C_states) indicies = numpy.array(range(len(real_E_states))) #Determine Inversion if numpy.any(self.real_E_states < 0) or numpy.any(numpy.logical_and(self.real_PMEFull_states < 0,numpy.array([i is not None for i in self.real_PMEFull_states]))): self.Inversion = True else: self.Inversion = False #Set the indicies, trap TypeError (logical_and false everywhere) as None (i.e. state not found in alchemy) if crunchC: #Check for the cap potential print "Not Coded Yet!" exit(1) #Create the Combinations basisVars = ["E", "A", "R", "C"] mappedStates = [self.real_E_states, self.real_R_states, self.real_C_states, self.real_A_states] nBasis = len(basisVars) coupled_states = {} decoupled_states = {} for iBasis in xrange(nBasis): coupled_states[basisVars[iBasis]] = numpy.where(mappedStates[iBasis] == 1.00)[0] #need the [0] to extract the array from the basis decoupled_states[basisVars[iBasis]] = numpy.where(mappedStates[iBasis] == 0.00)[0] self.coupled_states = coupled_states self.decoupled_states = decoupled_states self.basisVars = basisVars else: if self.PME_isolated: #Logic to solve for isolated PME case try: #Figure out the Fully coupled state self.real_EAR = int(indicies[ numpy.logical_and(numpy.logical_and(self.real_E_states == 1, self.real_PMEFull_states == 1), numpy.logical_and(self.real_R_states == 1, self.real_A_states == 1)) ]) except TypeError: self.real_EAR = None try: self.real_AR = int(indicies[ numpy.logical_and(numpy.logical_and(self.real_E_states == 0, self.real_PMEFull_states == 0), numpy.logical_and(self.real_R_states == 1, self.real_A_states == 1)) ]) except TypeError: self.real_AR = None try: self.real_R = int(indicies[ numpy.logical_and(numpy.logical_and(self.real_E_states == 0, self.real_PMEFull_states == 0), numpy.logical_and(self.real_R_states == 1, self.real_A_states == 0)) ]) except TypeError: self.real_R = None try: self.real_alloff = int(indicies[ numpy.logical_and(numpy.logical_and(self.real_E_states == 0, self.real_PMEFull_states == 0), numpy.logical_and(self.real_R_states == 0, self.real_A_states == 0)) ]) except: self.real_alloff = None try: self.real_PMEAR = int(indicies[ numpy.logical_and(numpy.logical_and(self.real_E_states == 0, self.real_PMEFull_states == 1), numpy.logical_and(self.real_R_states == 1, self.real_A_states == 1)) ]) except TypeError: self.real_PMEAR = None try: self.real_PMEsolve = int(indicies[ numpy.logical_and(numpy.logical_and(self.real_E_states == 0, numpy.logical_and(self.real_PMEFull_states != 1, self.real_PMEFull_states != 0)), numpy.logical_and(self.real_R_states == 1, self.real_A_states == 1)) ]) except TypeError: self.real_PMEsolve = None if self.Inversion: self.real_inverse = int(indicies[ numpy.logical_and(numpy.logical_and(numpy.logical_and(self.real_E_states == -1, self.real_PMEFull_states == -1), self.real_R_states == 1), self.real_A_states==1) ]) else: try: self.real_EAR = int(indicies[ numpy.logical_and(self.real_E_states == 1, numpy.logical_and(self.real_R_states == 1, self.real_A_states == 1)) ]) except TypeError: self.real_EAR = None try: self.real_AR = int(indicies[ numpy.logical_and(self.real_E_states == 0, numpy.logical_and(self.real_R_states == 1, self.real_A_states == 1)) ]) except TypeError: self.real_AR = None try: self.real_R = int(indicies[ numpy.logical_and(self.real_E_states == 0, numpy.logical_and(self.real_R_states == 1, self.real_A_states == 0)) ]) except TypeError: self.real_R = None try: self.real_alloff = int(indicies[ numpy.logical_and(self.real_E_states == 0, numpy.logical_and(self.real_R_states == 0, self.real_A_states == 0)) ]) except: self.real_alloff = None if self.Inversion: self.real_inverse = int(indicies[ numpy.logical_and(numpy.logical_and(self.real_E_states == -1, self.real_R_states == 1), self.real_A_states==1) ]) #Now that all the sorting and variable assignment has been done, must set the PME states which were not defined to the electrostatic state as thats how its coded (helps sorting algorithm later) #This algorighm also ensures that real_PMEFull_states is not dtype=object nstates = len(self.real_E_states) tempPME = numpy.zeros(nstates) for i in xrange(nstates): if self.real_PMEFull_states[i] is None: #Find where they are none tempPME[i] = self.real_E_states[i] #Assign them equal to the E state else: tempPME[i] = self.real_PMEFull_states[i] self.real_PMEFull_states = tempPME return
def _propagate_replica(self, replica_index): """ Attempt a Monte Carlo rotation/translation move followed by dynamics. """ # Create and cache Integrator and Context if needed. if not hasattr(self, '_context'): self._cache_context() # 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 cached integrator and context. integrator = self._integrator context = self._context # Set thermodynamic parameters for this state. integrator.setTemperature(state.temperature) integrator.setRandomNumberSeed(int(np.random.randint(0, MAX_SEED))) 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. try: barostat.setDefaultTemperature(state.temperature) except AttributeError: # versions previous to OpenMM0.8 barostat.setTemperature(state.temperature) barostat.setDefaultPressure(state.pressure) context.setParameter(barostat.Pressure(), state.pressure) # must be set in context barostat.setRandomNumberSeed( int(np.random.randint(0, MAX_SEED))) # Set alchemical state. AbsoluteAlchemicalFactory.perturbContext(context, state.alchemical_state) # Set box vectors. box_vectors = self.replica_box_vectors[replica_index] context.setPeriodicBoxVectors(box_vectors[0, :], box_vectors[1, :], box_vectors[2, :]) # Check if initial potential energy is NaN. reduced_potential = state.reduced_potential( self.replica_positions[replica_index], box_vectors=box_vectors, context=context) if np.isnan(reduced_potential): raise Exception( 'Initial potential for replica %d state %d is NaN before Monte Carlo displacement/rotation' % (replica_index, state_index)) # # Attempt a Monte Carlo rotation/translation move. # # 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, box_vectors=box_vectors, context=context) # 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, box_vectors=box_vectors, context=context) # Accept or reject with Metropolis criteria. du = u_new - u_old if (not np.isnan(u_new)) and ((du <= 0.0) or (np.random.rand() < np.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, box_vectors=box_vectors, context=context) # Compute new potential. perturbed_positions = self.propose_rotation( original_positions, self.mc_atoms) u_new = state.reduced_potential(perturbed_positions, box_vectors=box_vectors, context=context) du = u_new - u_old if (not np.isnan(u_new)) and ((du <= 0.0) or (np.random.rand() < np.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 dynamics. # start_time = time.time() # Run dynamics, retrying if NaNs are encountered. MAX_NAN_RETRIES = 6 nan_counter = 0 completed = False while (not completed): try: # Set box vectors. box_vectors = self.replica_box_vectors[replica_index] context.setPeriodicBoxVectors(box_vectors[0, :], box_vectors[1, :], box_vectors[2, :]) # Check if initial positions are NaN. positions = self.replica_positions[replica_index] if np.any(np.isnan(positions / unit.angstroms)): raise Exception( 'Initial particle positions for replica %d before propagation are NaN' % replica_index) # Set positions. positions = self.replica_positions[replica_index] context.setPositions(positions) setpositions_end_time = time.time() # Assign Maxwell-Boltzmann velocities. context.setVelocitiesToTemperature( state.temperature, int(np.random.randint(0, MAX_SEED))) setvelocities_end_time = time.time() # Check if initial potential energy is NaN. if np.isnan( context.getState(getEnergy=True).getPotentialEnergy() / state.kT): raise Exception( 'Potential for replica %d is NaN before dynamics' % replica_index) # Run dynamics. integrator.step(self.nsteps_per_iteration) integrator_end_time = time.time() # Get final positions getstate_start_time = time.time() openmm_state = context.getState( getPositions=True, enforcePeriodicBox=state.system. usesPeriodicBoundaryConditions()) getstate_end_time = time.time() # Check if final positions are NaN. positions = openmm_state.getPositions(asNumpy=True) if np.any(np.isnan(positions / unit.angstroms)): raise Exception('Particle coordinate is nan') # Get box vectors box_vectors = openmm_state.getPeriodicBoxVectors(asNumpy=True) # Check if final potential energy is NaN. if np.isnan( context.getState(getEnergy=True).getPotentialEnergy() / state.kT): raise Exception( 'Potential for replica %d is NaN after dynamics' % replica_index) # Signal completion completed = True except Exception as e: if str(e) == 'Particle coordinate is nan': # If it's a NaN, increment the NaN counter and try again nan_counter += 1 if nan_counter >= MAX_NAN_RETRIES: raise Exception( 'Maximum number of NAN retries (%d) exceeded.' % MAX_NAN_RETRIES) logger.info( 'NaN detected in replica %d. Retrying (%d / %d).' % (replica_index, nan_counter, MAX_NAN_RETRIES)) else: # It's not an exception we recognize, so re-raise it raise e # Store box vectors. self.replica_box_vectors[replica_index] = box_vectors # Store final positions self.replica_positions[replica_index] = positions # Compute timing. end_time = time.time() elapsed_time = end_time - start_time positions_elapsed_time = setpositions_end_time - start_time velocities_elapsed_time = setvelocities_end_time - setpositions_end_time integrator_elapsed_time = integrator_end_time - setvelocities_end_time getstate_elapsed_time = getstate_end_time - integrator_end_time logger.debug( "Replica %d/%d: integrator elapsed time %.3f s (positions %.3f s | velocities %.3f s | integrate+getstate %.3f s)." % (replica_index, self.nreplicas, elapsed_time, positions_elapsed_time, velocities_elapsed_time, integrator_elapsed_time + getstate_elapsed_time)) return elapsed_time
def __init__(self, **kwargs): super(LoopSoftening, self).__init__(**kwargs) self.description = 'Alchemical Loop Softening script' padding = 9.0*unit.angstrom explicit_solvent_model = 'tip3p' setup_path = 'data/mtor' # Create topology, positions, and system. from pkg_resources import resource_filename gaff_xml_filename = resource_filename('sams', 'data/gaff.xml') system_generators = dict() ffxmls = [gaff_xml_filename, 'amber99sbildn.xml', 'tip3p.xml'] forcefield_kwargs={ 'nonbondedMethod' : app.CutoffPeriodic, 'nonbondedCutoff' : 9.0 * unit.angstrom, 'implicitSolvent' : None, 'constraints' : app.HBonds, 'rigidWater' : True } # Load topologies and positions for all components print('Creating mTOR test system...') forcefield = app.ForceField(*ffxmls) from simtk.openmm.app import PDBFile, Modeller pdb_filename = resource_filename('sams', os.path.join(setup_path, 'mtor_pdbfixer_apo.pdb')) pdbfile = PDBFile(pdb_filename) modeller = app.Modeller(pdbfile.topology, pdbfile.positions) print('Adding solvent...') modeller.addSolvent(forcefield, model=explicit_solvent_model, padding=padding) self.topology = modeller.getTopology() self.positions = modeller.getPositions() print('Creating system...') self.system = forcefield.createSystem(self.topology, **forcefield_kwargs) # DEBUG: Write PDB outfile = open('initial.pdb', 'w') PDBFile.writeFile(self.topology, self.positions, outfile) outfile.close() # Atom Selection using MDtraj res_pairs = [[403, 483], [1052, 1109]] t = md.load(pdb_filename) alchemical_atoms = set() for x in res_pairs: start = min(t.top.select('residue %s' % min(x))) end = max(t.top.select('residue %s' % max(x))) + 1 alchemical_atoms.union(set(range(start, end))) # Create thermodynamic states. print('Creating alchemically-modified system...') temperature = 300 * unit.kelvin pressure = 1.0 * unit.atmospheres from alchemy import AbsoluteAlchemicalFactory factory = AbsoluteAlchemicalFactory(self.system, ligand_atoms=alchemical_atoms, annihilate_electrostatics=True, alchemical_torsions=True, annihilate_sterics=True, softcore_beta=0.0) # turn off softcore electrostatics self.system = factory.createPerturbedSystem() print('Setting up alchemical intermediates...') from sams import ThermodynamicState self.thermodynamic_states = list() for state in range(26): parameters = {'lambda_sterics' : 1.0, 'lambda_electrostatics' : (1.0 - float(state)/25.0) } self.thermodynamic_states.append( ThermodynamicState(system=self.system, temperature=temperature, parameters=parameters) ) for state in range(1,26): parameters = {'lambda_sterics' : (1.0 - float(state)/25.0), 'lambda_electrostatics' : 0.0 } self.thermodynamic_states.append( ThermodynamicState(system=self.system, temperature=temperature, parameters=parameters) ) #minimize(self.system, self.positions) minimize(self.system) # Create SAMS samplers print('Setting up 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.system.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.verbose = True self.exen_sampler = ExpandedEnsembleSampler(self.mcmc_sampler, self.thermodynamic_states) self.exen_sampler.verbose = True self.sams_sampler = SAMSSampler(self.exen_sampler) self.sams_sampler.verbose = True
def __init__(self, alchemical_protocol='two-phase', nlambda=50, **kwargs): """ Create an alchemical free energy calculation SAMS test system from the provided system. Parameters ---------- alchemical_protocol : str, optional, default='two-phase' Alchemical protocol scheme to use. ['two-phase', 'fused'] nlambda : int, optional, default=50 Number of alchemical states. """ super(AlchemicalSAMSTestSystem, self).__init__(**kwargs) self.description = 'Alchemical SAMS test system' self.alchemical_protocol = alchemical_protocol if not (hasattr(self, 'topology') and hasattr(self, 'system') and hasattr(self, 'positions') and hasattr(self, 'alchemical_atoms')): raise Exception("%s: 'topology', 'system', 'positions', and 'alchemical_atoms' properties must be defined!" % self.__class__.__name__) if not hasattr(self, 'temperature'): self.temperature = 300 * unit.kelvin if not hasattr(self, 'temperature'): self.temperature = 300 * unit.kelvin if not hasattr(self, 'pressure'): self.pressure = None # Add a MonteCarloBarostat if system does not have one has_barostat = False for force in self.system.getForces(): if force.__class__.__name__ in ['MonteCarloBarostat', 'MonteCarloAnisotropicBarostat']: has_barostat = True if (self.pressure is not None) and (not has_barostat): barostat = openmm.MonteCarloBarostat(self.pressure, self.temperature) self.system.addForce(barostat) # Create alchemically-modified system and populate thermodynamic states. from alchemy import AbsoluteAlchemicalFactory from sams import ThermodynamicState self.thermodynamic_states = list() if alchemical_protocol == 'fused': factory = AbsoluteAlchemicalFactory(self.system, ligand_atoms=self.alchemical_atoms, annihilate_electrostatics=True, annihilate_sterics=False) self.system = factory.createPerturbedSystem() from sams import ThermodynamicState alchemical_lambdas = np.linspace(1.0, 0.0, nlambda) for alchemical_lambda in alchemical_lambdas: parameters = {'lambda_sterics' : alchemical_lambda, 'lambda_electrostatics' : alchemical_lambda} self.thermodynamic_states.append( ThermodynamicState(system=self.system, temperature=self.temperature, pressure=self.pressure, parameters=parameters) ) elif alchemical_protocol == 'two-phase': factory = AbsoluteAlchemicalFactory(self.system, ligand_atoms=self.alchemical_atoms, annihilate_electrostatics=True, annihilate_sterics=False, softcore_beta=0.0) # turn off softcore electrostatics self.system = factory.createPerturbedSystem() nelec = int(nlambda/2.0) nvdw = nlambda - nelec for state in range(nelec+1): parameters = {'lambda_sterics' : 1.0, 'lambda_electrostatics' : (1.0 - float(state)/float(nelec)) } self.thermodynamic_states.append( ThermodynamicState(system=self.system, temperature=self.temperature, pressure=self.pressure, parameters=parameters) ) for state in range(1,nvdw+1): parameters = {'lambda_sterics' : (1.0 - float(state)/float(nvdw)), 'lambda_electrostatics' : 0.0 } self.thermodynamic_states.append( ThermodynamicState(system=self.system, temperature=self.temperature, pressure=self.pressure, parameters=parameters) ) else: raise Exception("'alchemical_protocol' must be one of ['two-phase', 'fused']; scheme '%s' unknown." % alchemical_protocol) # Create SAMS samplers print('Setting up 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.timestep = 2.0 * unit.femtoseconds self.mcmc_sampler.nsteps = 500 #self.mcmc_sampler.pdbfile = open('output.pdb', 'w') self.mcmc_sampler.topology = self.topology 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) self.sams_sampler.verbose = True # DEBUG: Write PDB of initial frame from simtk.openmm.app import PDBFile outfile = open('initial.pdb', 'w') PDBFile.writeFile(self.topology, self.positions, outfile) outfile.close()
#!/usr/bin/env python """ Create alchemical intermediates for default alchemical protocol for p-xylene in T4 lysozyme L99A in GBSA. """ from alchemy import AbsoluteAlchemicalFactory from openmmtools import testsystems # Create a reference system. print "Creating a reference T4 lysozyme L99A system..." complex = testsystems.LysozymeImplicit() [reference_system, positions] = [complex.system, complex.positions] # Create a factory to produce alchemical intermediates. print "Creating an alchemical factory..." receptor_atoms = range(0,2603) # T4 lysozyme L99A ligand_atoms = range(2603,2621) # p-xylene factory = AbsoluteAlchemicalFactory(reference_system, ligand_atoms=ligand_atoms) # Get the default protocol for 'denihilating' in complex in explicit solvent. protocol = factory.defaultComplexProtocolImplicit() # Create the perturbed systems using this protocol. print "Creating a perturbed system..." systems = factory.createPerturbedSystems(protocol) print "Done."
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 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 __init__(self, alchemical_protocol='two-phase', nlambda=50, **kwargs): """ Create an alchemical free energy calculation SAMS test system from the provided system. Parameters ---------- alchemical_protocol : str, optional, default='two-phase' Alchemical protocol scheme to use. ['two-phase', 'fused'] nlambda : int, optional, default=50 Number of alchemical states. """ super(AlchemicalSAMSTestSystem, self).__init__(**kwargs) self.description = 'Alchemical SAMS test system' self.alchemical_protocol = alchemical_protocol if not (hasattr(self, 'topology') and hasattr(self, 'system') and hasattr(self, 'positions') and hasattr(self, 'alchemical_atoms')): raise Exception( "%s: 'topology', 'system', 'positions', and 'alchemical_atoms' properties must be defined!" % self.__class__.__name__) if not hasattr(self, 'temperature'): self.temperature = 300 * unit.kelvin if not hasattr(self, 'temperature'): self.temperature = 300 * unit.kelvin if not hasattr(self, 'pressure'): self.pressure = None # Add a MonteCarloBarostat if system does not have one has_barostat = False for force in self.system.getForces(): if force.__class__.__name__ in [ 'MonteCarloBarostat', 'MonteCarloAnisotropicBarostat' ]: has_barostat = True if (self.pressure is not None) and (not has_barostat): barostat = openmm.MonteCarloBarostat(self.pressure, self.temperature) self.system.addForce(barostat) # Create alchemically-modified system and populate thermodynamic states. from alchemy import AbsoluteAlchemicalFactory from sams import ThermodynamicState self.thermodynamic_states = list() if alchemical_protocol == 'fused': factory = AbsoluteAlchemicalFactory( self.system, ligand_atoms=self.alchemical_atoms, annihilate_electrostatics=True, annihilate_sterics=False) self.system = factory.createPerturbedSystem() from sams import ThermodynamicState alchemical_lambdas = np.linspace(1.0, 0.0, nlambda) for alchemical_lambda in alchemical_lambdas: parameters = { 'lambda_sterics': alchemical_lambda, 'lambda_electrostatics': alchemical_lambda } self.thermodynamic_states.append( ThermodynamicState(system=self.system, temperature=self.temperature, pressure=self.pressure, parameters=parameters)) elif alchemical_protocol == 'two-phase': factory = AbsoluteAlchemicalFactory( self.system, ligand_atoms=self.alchemical_atoms, annihilate_electrostatics=True, annihilate_sterics=False, softcore_beta=0.0) # turn off softcore electrostatics self.system = factory.createPerturbedSystem() nelec = int(nlambda / 2.0) nvdw = nlambda - nelec for state in range(nelec + 1): parameters = { 'lambda_sterics': 1.0, 'lambda_electrostatics': (1.0 - float(state) / float(nelec)) } self.thermodynamic_states.append( ThermodynamicState(system=self.system, temperature=self.temperature, pressure=self.pressure, parameters=parameters)) for state in range(1, nvdw + 1): parameters = { 'lambda_sterics': (1.0 - float(state) / float(nvdw)), 'lambda_electrostatics': 0.0 } self.thermodynamic_states.append( ThermodynamicState(system=self.system, temperature=self.temperature, pressure=self.pressure, parameters=parameters)) else: raise Exception( "'alchemical_protocol' must be one of ['two-phase', 'fused']; scheme '%s' unknown." % alchemical_protocol) # Create SAMS samplers print('Setting up 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.timestep = 2.0 * unit.femtoseconds self.mcmc_sampler.nsteps = 500 #self.mcmc_sampler.pdbfile = open('output.pdb', 'w') self.mcmc_sampler.topology = self.topology 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) self.sams_sampler.verbose = True # DEBUG: Write PDB of initial frame from simtk.openmm.app import PDBFile outfile = open('initial.pdb', 'w') PDBFile.writeFile(self.topology, self.positions, outfile) outfile.close()
options['collision_rate'] = collision_rate options['platform'] = platform options['restraint_type'] = None if not is_periodic: options['restraint_type'] = 'harmonic' # Turn off MC ligand displacement. options['mc_displacement_sigma'] = None # Prepare phases of calculation. phase_prefixes = ['solvent', 'complex'] # list of calculation phases (thermodynamic legs) to set up components = ['ligand', 'receptor', 'solvent'] # components of the binding system phase_prefixes = ['complex'] # DEBUG, since 'solvent' doesn't work yet if is_periodic: protocols = {'complex': AbsoluteAlchemicalFactory.defaultComplexProtocolExplicit(), 'solvent': AbsoluteAlchemicalFactory.defaultSolventProtocolExplicit()} else: protocols = {'complex': AbsoluteAlchemicalFactory.defaultComplexProtocolImplicit(), 'solvent': AbsoluteAlchemicalFactory.defaultSolventProtocolImplicit()} alchemical_phases = [] # alchemical phases of the calculations for phase_prefix in phase_prefixes: # Retain the whole system if is_periodic: phase_suffix = 'explicit' else: phase_suffix = 'implicit' # Form phase name. phase = '%s-%s' % (phase_prefix, phase_suffix) logger.info("phase %s: " % phase)
barostat = openmm.MonteCarloBarostat(pressure, temperature) reference_system.addForce(barostat) # Identify ligand indices by residue name print('Identifying ligand atoms to be alchemically modified...') reference = md.load(pdb_filename) alchemical_atoms = reference.topology.select(ligand_dsl_selection) # these atoms will be alchemically softened alchemical_atoms = [ int(index) for index in alchemical_atoms ] # recode as Python int print("MDTraj DSL selection '%s' identified %d atoms" % (ligand_dsl_selection, len(alchemical_atoms))) # Create alchemically-modified system using fused softcore electrostatics and sterics print('Creating alchemically modified system...') print('lambda schedule: %s' % str(alchemical_lambdas)) from alchemy import AbsoluteAlchemicalFactory from sams import ThermodynamicState factory = AbsoluteAlchemicalFactory(reference_system, ligand_atoms=alchemical_atoms, annihilate_electrostatics=True, annihilate_sterics=False) system = factory.createPerturbedSystem() # Add umbrella restraint with global variable to control umbrella position print('umbrella schedule between atoms %d and %d: %s' % (umbrella_atoms[0], umbrella_atoms[1], str(umbrella_distances))) energy_function = '(umbrella_K/2.0)*(r-umbrella_r0)^2' umbrella_force = openmm.CustomBondForce(energy_function) umbrella_force.addGlobalParameter('umbrella_K', 0.0) # spring constant umbrella_force.addGlobalParameter('umbrella_r0', 0.0) # umbrella distance umbrella_force.addBond(umbrella_atoms[0], umbrella_atoms[1], []) umbrella_K = kT/umbrella_sigma**2 system.addForce(umbrella_force) # Create thermodynamic states thermodynamic_states = list() for alchemical_lambda in alchemical_lambdas:
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 _create_phase(self, thermodynamic_state, alchemical_phase, restraint_type): """ Create a repex object for a specified phase. Parameters ---------- thermodynamic_state : ThermodynamicState (System need not be defined) Thermodynamic state from which reference temperature and pressure are to be taken. alchemical_phase : AlchemicalPhase The alchemical phase to be created. restraint_type : str or None Restraint type to add between protein and ligand. Supported types are 'FlatBottom' and 'Harmonic'. The second one is available only in implicit solvent. """ # We add default repex options only on creation, on resume repex will pick them from the store file repex_parameters = { 'number_of_equilibration_iterations': 0, 'number_of_iterations': 100, 'timestep': 2.0 * unit.femtoseconds, 'collision_rate': 5.0 / unit.picoseconds, 'minimize': False, 'show_mixing_statistics': True, # this causes slowdown with iteration and should not be used for production 'displacement_sigma': 1.0 * unit.nanometers # attempt to displace ligand by this stddev will be made each iteration } repex_parameters.update(self._repex_parameters) # Convenience variables positions = alchemical_phase.positions reference_system = copy.deepcopy(alchemical_phase.reference_system) atom_indices = alchemical_phase.atom_indices alchemical_states = alchemical_phase.protocol # Check the dimensions of positions. for index in range(len(positions)): n_atoms, _ = (positions[index] / positions[index].unit).shape if n_atoms != reference_system.getNumParticles(): err_msg = "Phase {}: number of atoms in positions {} and and " \ "reference system differ ({} and {} respectively)" err_msg.format(alchemical_phase.name, index, n_atoms, reference_system.getNumParticles()) logger.error(err_msg) raise RuntimeError(err_msg) # Inizialize metadata storage. metadata = dict() # TODO: Use more general approach to determine whether system is periodic. is_periodic = reference_system.usesPeriodicBoundaryConditions() is_complex = len(atom_indices['receptor']) > 0 is_complex_explicit = is_complex and is_periodic is_complex_implicit = is_complex and not is_periodic # Make sure pressure is None if not periodic. if not is_periodic: thermodynamic_state.pressure = None # If temperature and pressure are specified, make sure MonteCarloBarostat is attached. elif thermodynamic_state.temperature and thermodynamic_state.pressure: forces = { reference_system.getForce(index).__class__.__name__ : reference_system.getForce(index) for index in range(reference_system.getNumForces()) } if 'MonteCarloAnisotropicBarostat' in forces: raise Exception('MonteCarloAnisotropicBarostat is unsupported.') if 'MonteCarloBarostat' in forces: logger.debug('MonteCarloBarostat found: Setting default temperature and pressure.') barostat = forces['MonteCarloBarostat'] # Set temperature and pressure. try: barostat.setDefaultTemperature(thermodynamic_state.temperature) except AttributeError: # versions previous to OpenMM7.1 barostat.setTemperature(thermodynamic_state.temperature) barostat.setDefaultPressure(thermodynamic_state.pressure) else: # Create barostat and add it to the system if it doesn't have one already. logger.debug('MonteCarloBarostat not found: Creating one.') barostat = openmm.MonteCarloBarostat(thermodynamic_state.pressure, thermodynamic_state.temperature) reference_system.addForce(barostat) # Store a serialized copy of the reference system. metadata['reference_system'] = openmm.XmlSerializer.serialize(reference_system) metadata['topology'] = utils.serialize_topology(alchemical_phase.reference_topology) # For explicit solvent calculations, we create a copy of the system for # which the fully-interacting energy is to be computed. An enlarged cutoff # is used to account for the anisotropic dispersion correction. This must # be done BEFORE adding the restraint to reference_system. # We dont care about restraint in decoupled state (although we will set to 0) since we will # account for that by hand. # Helper function for expanded cutoff def expand_cutoff(passed_system, max_allowed_cutoff = 16 * unit.angstroms): # Determine minimum box side dimension box_vectors = passed_system.getDefaultPeriodicBoxVectors() min_box_dimension = min([max(vector) for vector in box_vectors]) # Expand cutoff to minimize artifact and verify that box is big enough. # If we use a barostat we leave more room for volume fluctuations or # we risk fatal errors. If we don't use a barostat, OpenMM will raise # the appropriate exception on context creation. max_switching_distance = max_allowed_cutoff - (1 * unit.angstrom) # TODO: Make max_allowed_cutoff an option if thermodynamic_state.pressure and min_box_dimension < 2.25 * max_allowed_cutoff: raise RuntimeError('Barostated box sides must be at least 36 Angstroms ' 'to correct for missing dispersion interactions') logger.debug('Setting cutoff for fully interacting system to maximum ' 'allowed {}'.format(str(max_allowed_cutoff))) # Expanded cutoff system if needed # We don't want to reduce the cutoff if its already large for force in passed_system.getForces(): try: if force.getCutoffDistance() < max_allowed_cutoff: force.setCutoffDistance(max_allowed_cutoff) # Set switch distance # We don't need to check if we are using a switch since there is a setting for that. force.setSwitchingDistance(max_switching_distance) except Exception: pass try: if force.getCutoff() < max_allowed_cutoff: force.setCutoff(max_allowed_cutoff) except Exception: pass # Set the fully-interacting expanded cutoff state here if not is_periodic: fully_interacting_expanded_state = None else: # Create the fully interacting system fully_interacting_expanded_system = copy.deepcopy(reference_system) # Expand Cutoff expand_cutoff(fully_interacting_expanded_system) # Construct thermodynamic states fully_interacting_expanded_state = copy.deepcopy(thermodynamic_state) fully_interacting_expanded_state.system = fully_interacting_expanded_system # Compute standard state corrections for complex phase. metadata['standard_state_correction'] = 0.0 # TODO: Do we need to include a standard state correction for other phases in periodic boxes? if is_complex and restraint_type is not None: logger.debug("Creating receptor-ligand restraints...") reference_positions = positions[0] restraints = create_restraints(restraint_type, alchemical_phase.reference_topology, thermodynamic_state, reference_system, reference_positions, atom_indices['receptor'], atom_indices['ligand']) force = restraints.get_restraint_force() # Get Force object incorporating restraints. reference_system.addForce(force) metadata['standard_state_correction'] = restraints.get_standard_state_correction() # in kT elif is_complex_explicit: # For periodic systems, we must still add a standard state # correction for the box volume. # TODO: What if the box volume fluctuates during the simulation? box_vectors = reference_system.getDefaultPeriodicBoxVectors() box_volume = thermodynamic_state._volume(box_vectors) metadata['standard_state_correction'] = - np.log(V0 / box_volume) elif is_complex_implicit: # For implicit solvent/vacuum complex systems, we require a restraint # to keep the ligand from drifting too far away from receptor. raise ValueError('A receptor-ligand system in implicit solvent or ' 'vacuum requires a restraint.') # Create alchemically-modified states using alchemical factory. logger.debug("Creating alchemically-modified states...") try: alchemical_indices = atom_indices['ligand_counterions'] + atom_indices['ligand'] except KeyError: alchemical_indices = atom_indices['ligand'] factory = AbsoluteAlchemicalFactory(reference_system, ligand_atoms=alchemical_indices, **self._alchemy_parameters) alchemical_system = factory.alchemically_modified_system thermodynamic_state.system = alchemical_system # Create the expanded cutoff decoupled state if fully_interacting_expanded_state is None: noninteracting_expanded_state = None else: # Create the system for noninteracting expanded_factory = AbsoluteAlchemicalFactory(fully_interacting_expanded_state.system, ligand_atoms=alchemical_indices, **self._alchemy_parameters) noninteracting_expanded_system = expanded_factory.alchemically_modified_system # Set all USED alchemical interactions to the decoupled state alchemical_state = alchemical_states[-1] AbsoluteAlchemicalFactory.perturbSystem(noninteracting_expanded_system, alchemical_state) # Construct thermodynamic states noninteracting_expanded_state = copy.deepcopy(thermodynamic_state) noninteracting_expanded_state.system = noninteracting_expanded_system # Check systems for finite energies. # TODO: Refactor this into another function. finite_energy_check = False if finite_energy_check: logger.debug("Checking energies are finite for all alchemical systems.") integrator = openmm.VerletIntegrator(1.0 * unit.femtosecond) context = openmm.Context(alchemical_system, integrator) context.setPositions(positions[0]) for index, alchemical_state in enumerate(alchemical_states): AbsoluteAlchemicalFactory.perturbContext(context, alchemical_state) potential = context.getState(getEnergy=True).getPotentialEnergy() if np.isnan(potential / unit.kilocalories_per_mole): raise Exception("Energy for system %d is NaN." % index) del context, integrator logger.debug("All energies are finite.") # Randomize ligand position if requested, but only for implicit solvent systems. if self._randomize_ligand and is_complex_implicit: logger.debug("Randomizing ligand positions and excluding overlapping configurations...") randomized_positions = list() nstates = len(alchemical_states) for state_index in range(nstates): positions_index = np.random.randint(0, len(positions)) current_positions = positions[positions_index] new_positions = ModifiedHamiltonianExchange.randomize_ligand_position(current_positions, atom_indices['receptor'], atom_indices['ligand'], self._randomize_ligand_sigma_multiplier * restraints.getReceptorRadiusOfGyration(), self._randomize_ligand_close_cutoff) randomized_positions.append(new_positions) positions = randomized_positions if self._randomize_ligand and is_complex_explicit: logger.warning("Ligand randomization requested, but will not be performed for explicit solvent simulations.") # Identify whether any atoms will be displaced via MC, unless option is turned off. mc_atoms = None if self._mc_displacement_sigma: mc_atoms = list() if 'ligand' in atom_indices: mc_atoms = atom_indices['ligand'] # Set up simulation. # TODO: Support MPI initialization? logger.debug("Creating replica exchange object...") store_filename = os.path.join(self._store_directory, alchemical_phase.name + '.nc') self._store_filenames[alchemical_phase.name] = store_filename simulation = ModifiedHamiltonianExchange(store_filename, platform=self._platform) simulation.create(thermodynamic_state, alchemical_states, positions, displacement_sigma=self._mc_displacement_sigma, mc_atoms=mc_atoms, options=repex_parameters, metadata=metadata, fully_interacting_expanded_state = fully_interacting_expanded_state, noninteracting_expanded_state = noninteracting_expanded_state) # Initialize simulation. # TODO: Use the right scheme for initializing the simulation without running. #logger.debug("Initializing simulation...") #simulation.run(0) # Clean up simulation. del simulation # Add to list of phases that have been set up. self._phases.append(alchemical_phase.name) self._phases.sort() # sorting ensures all MPI processes run the same phase return
# DEBUG outfile = open('nacl-solvated.pdb', 'w') app.PDBFile.writeFile(topology, positions, file=outfile) outfile.close() # # Create alchemical intermediates. # print "Creating alchemical intermediates..." # Create a factory to produce alchemical intermediates. ligand_atoms = range(0, 2) # NaCl from alchemy import AbsoluteAlchemicalFactory factory = AbsoluteAlchemicalFactory(reference_system, ligand_atoms=ligand_atoms) # Get the default protocol for 'denihilating' in complex in explicit solvent. 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,
reference_system.addForce(barostat) # Identify ligand indices by residue name print('Identifying ligand atoms to be alchemically modified...') reference = md.load(pdb_filename) alchemical_atoms = reference.topology.select(ligand_dsl_selection) # these atoms will be alchemically softened alchemical_atoms = [ int(index) for index in alchemical_atoms ] # recode as Python int print("MDTraj DSL selection '%s' identified %d atoms" % (ligand_dsl_selection, len(alchemical_atoms))) # Create alchemically-modified system using fused softcore electrostatics and sterics print('Creating alchemically modified system...') print('lambda schedule: %s' % str(alchemical_lambdas)) from alchemy import AbsoluteAlchemicalFactory from sams import ThermodynamicState thermodynamic_states = list() factory = AbsoluteAlchemicalFactory(reference_system, ligand_atoms=alchemical_atoms, annihilate_electrostatics=True, annihilate_sterics=False) system = factory.createPerturbedSystem() for alchemical_lambda in alchemical_lambdas: parameters = {'lambda_sterics' : alchemical_lambda, 'lambda_electrostatics' : alchemical_lambda} thermodynamic_states.append( ThermodynamicState(system=system, temperature=temperature, pressure=pressure, parameters=parameters) ) # Select platform automatically; use mixed precision integrator = openmm.VerletIntegrator(timestep) context = openmm.Context(system, integrator) platform = context.getPlatform() del context platform.setPropertyDefaultValue('Precision', 'mixed') if platform.getName() == 'OpenCL': # GTX-1080 workaround platform.setPropertyDefaultValue('OpenCLDisablePmeStream', 'true')
def test_ncmc_alchemical_integrator_stability_molecules(): """ Test NCMCAlchemicalIntegrator """ molecule_names = ['pentane', 'biphenyl', 'imatinib'] if os.environ.get("TRAVIS", None) == 'true': molecule_names = ['pentane'] for molecule_name in molecule_names: from perses.tests.utils import createSystemFromIUPAC [molecule, system, positions, topology] = createSystemFromIUPAC(molecule_name) # Eliminate half of the molecule # TODO: Use a more rigorous scheme to make sure we are really cutting the molecule in half and not just eliminating hydrogens or something. alchemical_atoms = [ index for index in range(int(system.getNumParticles() / 2)) ] # Create an alchemically-modified system. from alchemy import AbsoluteAlchemicalFactory alchemical_factory = AbsoluteAlchemicalFactory( system, ligand_atoms=alchemical_atoms, annihilate_electrostatics=True, annihilate_sterics=True) # Return the alchemically-modified system in fully-interacting form. alchemical_system = alchemical_factory.createPerturbedSystem() # Create an NCMC switching integrator. from perses.annihilation.ncmc_switching import NCMCVVAlchemicalIntegrator temperature = 300.0 * unit.kelvin functions = { 'lambda_sterics': 'lambda', 'lambda_electrostatics': 'lambda^0.5', 'lambda_torsions': 'lambda', 'lambda_angles': 'lambda^2' } ncmc_integrator = NCMCVVAlchemicalIntegrator(temperature, alchemical_system, functions, direction='delete', nsteps=10, timestep=1.0 * unit.femtoseconds) # Create a Context context = openmm.Context(alchemical_system, ncmc_integrator) context.setPositions(positions) # Run the integrator ncmc_integrator.step(1) # Check positions are finite positions = context.getState(getPositions=True).getPositions( asNumpy=True) if np.isnan(np.any(positions / positions.unit)): raise Exception('NCMCAlchemicalIntegrator gave NaN positions') if np.isnan(ncmc_integrator.getLogAcceptanceProbability(context)): raise Exception( 'NCMCAlchemicalIntegrator gave NaN logAcceptanceProbability') del context, ncmc_integrator
""" Create alchemical intermediates for default alchemical protocol for one water in a water box. """ from alchemy import AbsoluteAlchemicalFactory, AlchemicalState from openmmtools import testsystems # Create a reference system. print "Creating a water box..." waterbox = testsystems.WaterBox() [reference_system, positions] = [waterbox.system, waterbox.positions] # Create a factory to produce alchemical intermediates. print "Creating an alchemical factory..." factory = AbsoluteAlchemicalFactory(reference_system, ligand_atoms=[0, 1, 2]) # Create a perturbed systems using this protocol. print "Creating a perturbed system..." alchemical_state = AlchemicalState() alchemical_system = factory.createPerturbedSystem(alchemical_state) # Perturb this system. print "Perturbing the system..." alchemical_state = AlchemicalState(lambda_sterics=0.90, lambda_electrostatics=0.90) factory.perturbSystem(alchemical_system, alchemical_state)
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 setup_binding_amber(args): """ Set up ligand binding free energy calculation using AMBER prmtop/inpcrd files. Parameters ---------- args : dict Command-line arguments dict from docopt. Returns ------- alchemical_phases : list of AlchemicalPhase Phases (thermodynamic legs) of the calculation. """ verbose = args['--verbose'] setup_directory = args[ '--setupdir'] # Directory where prmtop/inpcrd files are to be found system_parameters = {} # parameters to pass to prmtop.createSystem # Implicit solvent if args['--gbsa']: system_parameters['implicitSolvent'] = getattr(app, args['--gbsa']) # Select nonbonded treatment if args['--nbmethod']: system_parameters['nonbondedMethod'] = getattr(app, args['--nbmethod']) # Constraints if args['--constraints']: system_parameters['constraints'] = getattr(app, args['--constraints']) # Cutoff if args['--cutoff']: system_parameters['nonbondedCutoff'] = process_unit_bearing_arg( args, '--cutoff', unit.nanometers) # Determine if this will be an explicit or implicit solvent simulation if ('nonbondedMethod' in system_parameters and system_parameters['nonbondedMethod'] != app.NoCutoff): phases_names = ['complex-explicit', 'solvent-explicit'] protocols = [ AbsoluteAlchemicalFactory.defaultComplexProtocolExplicit(), AbsoluteAlchemicalFactory.defaultSolventProtocolExplicit() ] else: phases_names = ['complex-implicit', 'solvent-implicit'] protocols = [ AbsoluteAlchemicalFactory.defaultComplexProtocolImplicit(), AbsoluteAlchemicalFactory.defaultSolventProtocolImplicit() ] # Prepare Yank arguments alchemical_phases = [None, None] setup_directory = os.path.join(setup_directory, '') # add final slash character system_files_paths = [[ setup_directory + 'complex.inpcrd', setup_directory + 'complex.prmtop' ], [ setup_directory + 'solvent.inpcrd', setup_directory + 'solvent.prmtop' ]] for i, phase_name in enumerate(phases_names): positions_file_path = system_files_paths[i][0] topology_file_path = system_files_paths[i][1] logger.info("Reading phase {}".format(phase_name)) alchemical_phases[i] = pipeline.prepare_phase(positions_file_path, topology_file_path, args['--ligand'], system_parameters, verbose=verbose) alchemical_phases[i].name = phase_name alchemical_phases[i].protocol = protocols[i] return alchemical_phases
def testAlchemicalFactory( reference_system, coordinates, receptor_atoms, ligand_atoms, platform_name="CUDA", 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 platform = openmm.Platform.getPlatformByName(platform_name) delta = 0.001 delta = 1.0e-6 compareSystemEnergies( coordinates, [ reference_system, factory.createPerturbedSystem( AlchemicalState( 0, 1 - delta, 1, 1, annihilateElectrostatics=annihilateElectrostatics, annihilateLennardJones=annihilateLennardJones, ) ), ], ["reference", "partially discharged"], platform=platform, ) compareSystemEnergies( coordinates, [ factory.createPerturbedSystem( AlchemicalState( 0, delta, 1, 1, annihilateElectrostatics=annihilateElectrostatics, annihilateLennardJones=annihilateLennardJones, ) ), factory.createPerturbedSystem( AlchemicalState( 0, 0.0, 1, 1, annihilateElectrostatics=annihilateElectrostatics, annihilateLennardJones=annihilateLennardJones, ) ), ], ["partially charged", "discharged"], platform=platform, ) compareSystemEnergies( coordinates, [ factory.createPerturbedSystem( AlchemicalState( 0, 0, 1, 1, annihilateElectrostatics=annihilateElectrostatics, annihilateLennardJones=annihilateLennardJones, ) ), factory.createPerturbedSystem( AlchemicalState( 0, 0, 1 - delta, 1, annihilateElectrostatics=annihilateElectrostatics, annihilateLennardJones=annihilateLennardJones, ) ), ], ["discharged", "partially decoupled"], platform=platform, ) compareSystemEnergies( coordinates, [ factory.createPerturbedSystem( AlchemicalState( 0, 0, delta, 1, annihilateElectrostatics=annihilateElectrostatics, annihilateLennardJones=annihilateLennardJones, ) ), factory.createPerturbedSystem( AlchemicalState( 0, 0, 0, 1, annihilateElectrostatics=annihilateElectrostatics, annihilateLennardJones=annihilateLennardJones, ) ), ], ["partially coupled", "decoupled"], platform=platform, ) return
def __init__(self, store_directory, mpicomm=None, **kwargs): """ Initialize YANK object with default parameters. Parameters ---------- store_directory : str The storage directory in which output NetCDF files are read or written. mpicomm : MPI communicator, optional If an MPI communicator is passed, an MPI simulation will be attempted. restraint_type : str, optional Restraint type to add between protein and ligand. Supported types are 'flat-bottom' and 'harmonic'. The second one is available only in implicit solvent (default: 'flat-bottom'). randomize_ligand : bool, optional Randomize ligand position when True. Not available in explicit solvent (default: False). randomize_ligand_close_cutoff : simtk.unit.Quantity (units: length), optional Cutoff for ligand position randomization (default: 1.5*unit.angstrom). randomize_ligand_sigma_multiplier : float, optional Multiplier for ligand position randomization displacement (default: 2.0). mc_displacement_sigma : simtk.unit.Quantity (units: length), optional Maximum displacement for Monte Carlo moves that augment Langevin dynamics (default: 10.0*unit.angstrom). Other Parameters ---------------- **kwargs More options to pass to the ReplicaExchange or AlchemicalFactory classes on initialization. See Also -------- ReplicaExchange.default_parameters : extra parameters accepted. """ # Copy kwargs to avoid modifications parameters = copy.deepcopy(kwargs) # Record that we are not yet initialized. self._initialized = False # Store output directory. self._store_directory = store_directory # Save MPI communicator self._mpicomm = mpicomm # Set internal variables. self._phases = list() self._store_filenames = dict() # Default alchemical protocols. self.default_protocols = dict() self.default_protocols['vacuum'] = AbsoluteAlchemicalFactory.defaultVacuumProtocol() self.default_protocols['solvent-implicit'] = AbsoluteAlchemicalFactory.defaultSolventProtocolImplicit() self.default_protocols['complex-implicit'] = AbsoluteAlchemicalFactory.defaultComplexProtocolImplicit() self.default_protocols['solvent-explicit'] = AbsoluteAlchemicalFactory.defaultSolventProtocolExplicit() self.default_protocols['complex-explicit'] = AbsoluteAlchemicalFactory.defaultComplexProtocolExplicit() # Store Yank parameters for option_name, default_value in self.default_parameters.items(): setattr(self, '_' + option_name, parameters.pop(option_name, default_value)) # Store repex parameters self._repex_parameters = {par: parameters.pop(par) for par in ModifiedHamiltonianExchange.default_parameters if par in parameters} # Store AlchemicalFactory parameters self._alchemy_parameters = {par: parameters.pop(par) for par in inspect.getargspec(AbsoluteAlchemicalFactory.__init__).args if par in parameters} # Check for unknown parameters if len(parameters) > 0: raise TypeError('got an unexpected keyword arguments {}'.format( ', '.join(parameters.keys())))
def setup_binding_gromacs(args): """ Set up ligand binding free energy calculation using gromacs prmtop/inpcrd files. Parameters ---------- args : dict Command-line arguments dict from docopt. Returns ------- alchemical_phases : list of AlchemicalPhase Phases (thermodynamic legs) of the calculation. """ verbose = args['--verbose'] # Implicit solvent if args['--gbsa']: implicitSolvent = getattr(app, args['--gbsa']) else: implicitSolvent = None # Select nonbonded treatment # TODO: Carefully check whether input file is periodic or not. if args['--nbmethod']: nonbondedMethod = getattr(app, args['--nbmethod']) else: nonbondedMethod = None # Constraints if args['--constraints']: constraints = getattr(app, args['--constraints']) else: constraints = None # Cutoff if args['--cutoff']: nonbondedCutoff = process_unit_bearing_arg(args, '--cutoff', unit.nanometers) else: nonbondedCutoff = None # COM removal removeCMMotion = False # Prepare phases of calculation. phase_prefixes = ['solvent', 'complex'] # list of calculation phases (thermodynamic legs) to set up components = ['ligand', 'receptor', 'solvent'] # components of the binding system systems = dict() # systems[phase] is the System object associated with phase 'phase' topologies = dict() # topologies[phase] is the Topology object associated with phase 'phase' positions = dict() # positions[phase] is a list of coordinates associated with phase 'phase' atom_indices = dict() # ligand_atoms[phase] is a list of ligand atom indices associated with phase 'phase' setup_directory = args['--setupdir'] # Directory where prmtop/inpcrd files are to be found for phase_prefix in phase_prefixes: if verbose: logger.info("reading phase %s: " % phase_prefix) # Read gromacs input files. gro_filename = os.path.join(setup_directory, '%s.gro' % phase_prefix) top_filename = os.path.join(setup_directory, '%s.top' % phase_prefix) if verbose: logger.info('reading gromacs .gro file: %s' % gro_filename) gro = app.GromacsGroFile(gro_filename) if verbose: logger.info('reading gromacs .top file "%s" using gromacs include directory "%s"' % (top_filename, args['--gromacsinclude'])) top = app.GromacsTopFile(top_filename, unitCellDimensions=gro.getUnitCellDimensions(), includeDir=args['--gromacsinclude']) # Assume explicit solvent. # TODO: Modify this if we can have implicit solvent. is_periodic = True phase_suffix = 'explicit' # Adjust nonbondedMethod. # TODO: Ensure that selected method is appropriate. if nonbondedMethod == None: if is_periodic: nonbondedMethod = app.CutoffPeriodic else: nonbondedMethod = app.NoCutoff # TODO: Check to make sure both prmtop and inpcrd agree on explicit/implicit. phase = '%s-%s' % (phase_prefix, phase_suffix) systems[phase] = top.createSystem(nonbondedMethod=nonbondedMethod, nonbondedCutoff=nonbondedCutoff, constraints=constraints, removeCMMotion=removeCMMotion) topologies[phase] = top.topology positions[phase] = gro.getPositions(asNumpy=True) # Check to make sure number of atoms match between prmtop and inpcrd. prmtop_natoms = systems[phase].getNumParticles() inpcrd_natoms = positions[phase].shape[0] if prmtop_natoms != inpcrd_natoms: raise Exception("Atom number mismatch: prmtop %s has %d atoms; inpcrd %s has %d atoms." % (prmtop_filename, prmtop_natoms, inpcrd_filename, inpcrd_natoms)) # Find ligand atoms and receptor atoms. ligand_dsl = args['--ligand'] # MDTraj DSL that specifies ligand atoms atom_indices[phase] = find_components(systems[phase], top.topology, ligand_dsl) phases = systems.keys() alchemical_phases = [None, None] protocols = {'complex-explicit': AbsoluteAlchemicalFactory.defaultComplexProtocolExplicit(), 'solvent-explicit': AbsoluteAlchemicalFactory.defaultSolventProtocolImplicit()} for i, name in enumerate(phases): alchemical_phases[i] = AlchemicalPhase(name, systems[name], topologies[name], positions[name], atom_indices[name], protocols[name]) return alchemical_phases
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' nstates = len(systems) #============================================================================== # Run simulation. #============================================================================== # Initialize NetCDF file to store data. import netCDF4 as netcdf ncfile = netcdf.Dataset(filename, 'w', version='NETCDF4') ncfile.createDimension('iteration', 0) # unlimited number of iterations ncfile.createDimension('state', nstates) # number of replicas ncfile.createDimension('atom', reference_system.getNumParticles()) # number of atoms in system ncfile.createDimension('spatial', 3) # number of spatial dimensions ncfile.createVariable('positions', 'f', ('iteration','state','atom','spatial')) # positions (in A)
def _create_phase(self, thermodynamic_state, alchemical_phase): """ Create a repex object for a specified phase. Parameters ---------- thermodynamic_state : ThermodynamicState (System need not be defined) Thermodynamic state from which reference temperature and pressure are to be taken. alchemical_phase : AlchemicalPhase The alchemical phase to be created. """ # We add default repex options only on creation, on resume repex will pick them from the store file repex_parameters = { 'number_of_equilibration_iterations': 0, 'number_of_iterations': 100, 'timestep': 2.0 * unit.femtoseconds, 'collision_rate': 5.0 / unit.picoseconds, 'minimize': False, 'show_mixing_statistics': True, # this causes slowdown with iteration and should not be used for production 'displacement_sigma': 1.0 * unit. nanometers # attempt to displace ligand by this stddev will be made each iteration } repex_parameters.update(self._repex_parameters) # Convenience variables positions = alchemical_phase.positions reference_system = copy.deepcopy(alchemical_phase.reference_system) atom_indices = alchemical_phase.atom_indices alchemical_states = alchemical_phase.protocol # If temperature and pressure are specified, make sure MonteCarloBarostat is attached. if thermodynamic_state.temperature and thermodynamic_state.pressure: forces = { reference_system.getForce(index).__class__.__name__: reference_system.getForce(index) for index in range(reference_system.getNumForces()) } if 'MonteCarloAnisotropicBarostat' in forces: raise Exception( 'MonteCarloAnisotropicBarostat is unsupported.') if 'MonteCarloBarostat' in forces: logger.debug( 'MonteCarloBarostat found: Setting default temperature and pressure.' ) barostat = forces['MonteCarloBarostat'] # Set temperature and pressure. try: barostat.setDefaultTemperature( thermodynamic_state.temperature) except AttributeError: # versions previous to OpenMM7.1 barostat.setTemperature(thermodynamic_state.temperature) barostat.setDefaultPressure(state.pressure) else: # Create barostat and add it to the system if it doesn't have one already. logger.debug('MonteCarloBarostat not found: Creating one.') barostat = openmm.MonteCarloBarostat( thermodynamic_state.pressure, thermodynamic_state.temperature) reference_system.addForce(barostat) # Check the dimensions of positions. for index in range(len(positions)): n_atoms, _ = (positions[index] / positions[index].unit).shape if n_atoms != reference_system.getNumParticles(): err_msg = "Phase {}: number of atoms in positions {} and and " \ "reference system differ ({} and {} respectively)" err_msg.format(alchemical_phase.name, index, n_atoms, reference_system.getNumParticles()) logger.error(err_msg) raise RuntimeError(err_msg) # Inizialize metadata storage. metadata = dict() # Store a serialized copy of the reference system. metadata['reference_system'] = openmm.XmlSerializer.serialize( reference_system) metadata['topology'] = utils.serialize_topology( alchemical_phase.reference_topology) # TODO: Use more general approach to determine whether system is periodic. is_periodic = self._is_periodic(reference_system) is_complex_explicit = len(atom_indices['receptor']) > 0 and is_periodic is_complex_implicit = len( atom_indices['receptor']) > 0 and not is_periodic # Make sure pressure is None if not periodic. if not is_periodic: thermodynamic_state.pressure = None # Create a copy of the system for which the fully-interacting energy is to be computed. # For explicit solvent calculations, an enlarged cutoff is used to account for the anisotropic dispersion correction. fully_interacting_system = copy.deepcopy(reference_system) if is_periodic: # Expand cutoff to maximum allowed # TODO: Should we warn if cutoff can't be extended enough? # TODO: Should we extend to some minimum cutoff rather than the maximum allowed? box_vectors = fully_interacting_system.getDefaultPeriodicBoxVectors( ) max_allowed_cutoff = 0.499 * max([ max(vector) for vector in box_vectors ]) # TODO: Correct this for non-rectangular boxes logger.debug( 'Setting cutoff for fully interacting system to maximum allowed (%s)' % str(max_allowed_cutoff)) for force_index in range(fully_interacting_system.getNumForces()): force = fully_interacting_system.getForce(force_index) if hasattr(force, 'setCutoffDistance'): force.setCutoffDistance(max_allowed_cutoff) if hasattr(force, 'setCutoff'): force.setCutoff(max_allowed_cutoff) # Construct thermodynamic state fully_interacting_state = copy.deepcopy(thermodynamic_state) fully_interacting_state.system = fully_interacting_system # Compute standard state corrections for complex phase. metadata['standard_state_correction'] = 0.0 # TODO: Do we need to include a standard state correction for other phases in periodic boxes? if is_complex_implicit: # Impose restraints for complex system in implicit solvent to keep ligand from drifting too far away from receptor. logger.debug("Creating receptor-ligand restraints...") reference_positions = positions[0] if self._restraint_type == 'harmonic': restraints = HarmonicReceptorLigandRestraint( thermodynamic_state, reference_system, reference_positions, atom_indices['receptor'], atom_indices['ligand']) elif self._restraint_type == 'flat-bottom': restraints = FlatBottomReceptorLigandRestraint( thermodynamic_state, reference_system, reference_positions, atom_indices['receptor'], atom_indices['ligand']) else: raise Exception("restraint_type of '%s' is not supported." % self._restraint_type) force = restraints.getRestraintForce( ) # Get Force object incorporating restraints reference_system.addForce(force) metadata[ 'standard_state_correction'] = restraints.getStandardStateCorrection( ) # standard state correction in kT elif is_complex_explicit: # For periodic systems, we do not use a restraint, but must add a standard state correction for the box volume. # TODO: What if the box volume fluctuates during the simulation? box_vectors = reference_system.getDefaultPeriodicBoxVectors() box_volume = thermodynamic_state._volume(box_vectors) STANDARD_STATE_VOLUME = 1660.53928 * unit.angstrom**3 metadata['standard_state_correction'] = -np.log( STANDARD_STATE_VOLUME / box_volume) # Create alchemically-modified states using alchemical factory. logger.debug("Creating alchemically-modified states...") try: alchemical_indices = atom_indices[ 'ligand_counterions'] + atom_indices['ligand'] except KeyError: alchemical_indices = atom_indices['ligand'] factory = AbsoluteAlchemicalFactory(reference_system, ligand_atoms=alchemical_indices, **self._alchemy_parameters) alchemical_system = factory.alchemically_modified_system thermodynamic_state.system = alchemical_system # Check systems for finite energies. # TODO: Refactor this into another function. finite_energy_check = False if finite_energy_check: logger.debug( "Checking energies are finite for all alchemical systems.") integrator = openmm.VerletIntegrator(1.0 * unit.femtosecond) context = openmm.Context(alchemical_system, integrator) context.setPositions(positions[0]) for alchemical_state in alchemical_states: AbsoluteAlchemicalFactory.perturbContext( context, alchemical_state) potential = context.getState( getEnergy=True).getPotentialEnergy() if np.isnan(potential / unit.kilocalories_per_mole): raise Exception("Energy for system %d is NaN." % index) del context, integrator logger.debug("All energies are finite.") # Randomize ligand position if requested, but only for implicit solvent systems. if self._randomize_ligand and is_complex_implicit: logger.debug( "Randomizing ligand positions and excluding overlapping configurations..." ) randomized_positions = list() nstates = len(alchemical_states) for state_index in range(nstates): positions_index = np.random.randint(0, len(positions)) current_positions = positions[positions_index] new_positions = ModifiedHamiltonianExchange.randomize_ligand_position( current_positions, atom_indices['receptor'], atom_indices['ligand'], self._randomize_ligand_sigma_multiplier * restraints.getReceptorRadiusOfGyration(), self._randomize_ligand_close_cutoff) randomized_positions.append(new_positions) positions = randomized_positions if self._randomize_ligand and is_complex_explicit: logger.warning( "Ligand randomization requested, but will not be performed for explicit solvent simulations." ) # Identify whether any atoms will be displaced via MC, unless option is turned off. mc_atoms = None if self._mc_displacement_sigma: mc_atoms = list() if 'ligand' in atom_indices: mc_atoms = atom_indices['ligand'] # Set up simulation. # TODO: Support MPI initialization? logger.debug("Creating replica exchange object...") store_filename = os.path.join(self._store_directory, alchemical_phase.name + '.nc') self._store_filenames[alchemical_phase.name] = store_filename simulation = ModifiedHamiltonianExchange(store_filename) simulation.create(thermodynamic_state, alchemical_states, positions, displacement_sigma=self._mc_displacement_sigma, mc_atoms=mc_atoms, options=repex_parameters, metadata=metadata, fully_interacting_state=fully_interacting_state) # Initialize simulation. # TODO: Use the right scheme for initializing the simulation without running. #logger.debug("Initializing simulation...") #simulation.run(0) # Clean up simulation. del simulation # Add to list of phases that have been set up. self._phases.append(alchemical_phase.name) return
def setup_binding_gromacs(args): """ Set up ligand binding free energy calculation using gromacs prmtop/inpcrd files. Parameters ---------- args : dict Command-line arguments dict from docopt. Returns ------- alchemical_phases : list of AlchemicalPhase Phases (thermodynamic legs) of the calculation. """ verbose = args['--verbose'] # Implicit solvent if args['--gbsa']: implicitSolvent = getattr(app, args['--gbsa']) else: implicitSolvent = None # Select nonbonded treatment # TODO: Carefully check whether input file is periodic or not. if args['--nbmethod']: nonbondedMethod = getattr(app, args['--nbmethod']) else: nonbondedMethod = None # Constraints if args['--constraints']: constraints = getattr(app, args['--constraints']) else: constraints = None # Cutoff if args['--cutoff']: nonbondedCutoff = process_unit_bearing_arg(args, '--cutoff', unit.nanometers) else: nonbondedCutoff = None # COM removal removeCMMotion = False # Prepare phases of calculation. phase_prefixes = [ 'solvent', 'complex' ] # list of calculation phases (thermodynamic legs) to set up components = ['ligand', 'receptor', 'solvent'] # components of the binding system systems = dict( ) # systems[phase] is the System object associated with phase 'phase' topologies = dict( ) # topologies[phase] is the Topology object associated with phase 'phase' positions = dict( ) # positions[phase] is a list of coordinates associated with phase 'phase' atom_indices = dict( ) # ligand_atoms[phase] is a list of ligand atom indices associated with phase 'phase' setup_directory = args[ '--setupdir'] # Directory where prmtop/inpcrd files are to be found for phase_prefix in phase_prefixes: if verbose: logger.info("reading phase %s: " % phase_prefix) # Read gromacs input files. gro_filename = os.path.join(setup_directory, '%s.gro' % phase_prefix) top_filename = os.path.join(setup_directory, '%s.top' % phase_prefix) if verbose: logger.info('reading gromacs .gro file: %s' % gro_filename) gro = app.GromacsGroFile(gro_filename) if verbose: logger.info( 'reading gromacs .top file "%s" using gromacs include directory "%s"' % (top_filename, args['--gromacsinclude'])) top = app.GromacsTopFile( top_filename, unitCellDimensions=gro.getUnitCellDimensions(), includeDir=args['--gromacsinclude']) # Assume explicit solvent. # TODO: Modify this if we can have implicit solvent. is_periodic = True phase_suffix = 'explicit' # Adjust nonbondedMethod. # TODO: Ensure that selected method is appropriate. if nonbondedMethod == None: if is_periodic: nonbondedMethod = app.CutoffPeriodic else: nonbondedMethod = app.NoCutoff # TODO: Check to make sure both prmtop and inpcrd agree on explicit/implicit. phase = '%s-%s' % (phase_prefix, phase_suffix) systems[phase] = top.createSystem(nonbondedMethod=nonbondedMethod, nonbondedCutoff=nonbondedCutoff, constraints=constraints, removeCMMotion=removeCMMotion) topologies[phase] = top.topology positions[phase] = gro.getPositions(asNumpy=True) # Check to make sure number of atoms match between prmtop and inpcrd. prmtop_natoms = systems[phase].getNumParticles() inpcrd_natoms = positions[phase].shape[0] if prmtop_natoms != inpcrd_natoms: raise Exception( "Atom number mismatch: prmtop %s has %d atoms; inpcrd %s has %d atoms." % (prmtop_filename, prmtop_natoms, inpcrd_filename, inpcrd_natoms)) # Find ligand atoms and receptor atoms. ligand_dsl = args['--ligand'] # MDTraj DSL that specifies ligand atoms atom_indices[phase] = find_components(systems[phase], top.topology, ligand_dsl) phases = systems.keys() alchemical_phases = [None, None] protocols = { 'complex-explicit': AbsoluteAlchemicalFactory.defaultComplexProtocolExplicit(), 'solvent-explicit': AbsoluteAlchemicalFactory.defaultSolventProtocolImplicit() } for i, name in enumerate(phases): alchemical_phases[i] = AlchemicalPhase(name, systems[name], topologies[name], positions[name], atom_indices[name], protocols[name]) return alchemical_phases
def _create_phase(self, phase, reference_system, positions, atom_indices, thermodynamic_state, protocols=None, options=None, mpicomm=None): """ Create a repex object for a specified phase. Parameters ---------- phase : str The phase being initialized (one of ['complex', 'solvent', 'vacuum']) reference_system : simtk.openmm.System The reference system object from which alchemical intermediates are to be construcfted. positions : list of simtk.unit.Qunatity objects containing (natoms x 3) positions (as np or lists) The list of positions to be used to seed replicas in a round-robin way. atom_indices : dict atom_indices[phase][component] is the set of atom indices associated with component, where component is ['ligand', 'receptor'] thermodynamic_state : ThermodynamicState Thermodynamic state from which reference temperature and pressure are to be taken. protocols : dict of list of AlchemicalState, optional, default=None If specified, the alchemical protocol protocols[phase] will be used for phase 'phase' instead of the default. options : dict of str, optional, default=None If specified, these options will override default repex simulation options. """ # Combine simulation options with defaults to create repex options. repex_options = dict(self.default_options.items() + options.items()) # Make sure positions argument is a list of coordinate snapshots. if hasattr(positions, 'unit'): # Wrap in list. positions = [positions] # Check the dimensions of positions. for index in range(len(positions)): # Make sure it is recast as a np array. positions[index] = unit.Quantity(np.array(positions[index] / positions[index].unit), positions[index].unit) [natoms, ndim] = (positions[index] / positions[index].unit).shape if natoms != reference_system.getNumParticles(): raise Exception("positions argument must be a list of simtk.unit.Quantity of (natoms,3) lists or np array with units compatible with nanometers.") # Create metadata storage. metadata = dict() # Make a deep copy of the reference system so we don't accidentally modify it. reference_system = copy.deepcopy(reference_system) # TODO: Use more general approach to determine whether system is periodic. is_periodic = self._is_periodic(reference_system) # Make sure pressure is None if not periodic. if not is_periodic: thermodynamic_state.pressure = None # Compute standard state corrections for complex phase. metadata['standard_state_correction'] = 0.0 # TODO: Do we need to include a standard state correction for other phases in periodic boxes? if phase == 'complex-implicit': # Impose restraints for complex system in implicit solvent to keep ligand from drifting too far away from receptor. logger.debug("Creating receptor-ligand restraints...") reference_positions = positions[0] if self.restraint_type == 'harmonic': restraints = HarmonicReceptorLigandRestraint(thermodynamic_state, reference_system, reference_positions, atom_indices['receptor'], atom_indices['ligand']) elif self.restraint_type == 'flat-bottom': restraints = FlatBottomReceptorLigandRestraint(thermodynamic_state, reference_system, reference_positions, atom_indices['receptor'], atom_indices['ligand']) else: raise Exception("restraint_type of '%s' is not supported." % self.restraint_type) force = restraints.getRestraintForce() # Get Force object incorporating restraints reference_system.addForce(force) metadata['standard_state_correction'] = restraints.getStandardStateCorrection() # standard state correction in kT elif phase == 'complex-explicit': # For periodic systems, we do not use a restraint, but must add a standard state correction for the box volume. # TODO: What if the box volume fluctuates during the simulation? box_vectors = reference_system.getDefaultPeriodicBoxVectors() box_volume = thermodynamic_state._volume(box_vectors) STANDARD_STATE_VOLUME = 1660.53928 * unit.angstrom**3 metadata['standard_state_correction'] = np.log(STANDARD_STATE_VOLUME / box_volume) # TODO: Check sign. # Use default alchemical protocols if not specified. if not protocols: protocols = self.default_protocols # Create alchemically-modified states using alchemical factory. logger.debug("Creating alchemically-modified states...") #factory = AbsoluteAlchemicalFactory(reference_system, ligand_atoms=atom_indices['ligand'], test_positions=positions[0], platform=repex_options['platform']) factory = AbsoluteAlchemicalFactory(reference_system, ligand_atoms=atom_indices['ligand']) alchemical_states = protocols[phase] alchemical_system = factory.alchemically_modified_system thermodynamic_state.system = alchemical_system # Check systems for finite energies. finite_energy_check = False if finite_energy_check: logger.debug("Checking energies are finite for all alchemical systems.") integrator = openmm.VerletIntegrator(1.0 * unit.femtosecond) context = openmm.Context(alchemical_system, integrator) context.setPositions(positions[0]) for alchemical_state in alchemical_states: AbsoluteAlchemicalFactory.perturbContext(context, alchemical_state) potential = context.getState(getEnergy=True).getPotentialEnergy() if np.isnan(potential / unit.kilocalories_per_mole): raise Exception("Energy for system %d is NaN." % index) del context, integrator logger.debug("All energies are finite.") # Randomize ligand position if requested, but only for implicit solvent systems. if self.randomize_ligand and (phase == 'complex-implicit'): logger.debug("Randomizing ligand positions and excluding overlapping configurations...") randomized_positions = list() nstates = len(systems) for state_index in range(nstates): positions_index = np.random.randint(0, len(positions)) current_positions = positions[positions_index] new_positions = ModifiedHamiltonianExchange.randomize_ligand_position(current_positions, atom_indices['receptor'], atom_indices['ligand'], self.randomize_ligand_sigma_multiplier * restraints.getReceptorRadiusOfGyration(), self.randomize_ligand_close_cutoff) randomized_positions.append(new_positions) positions = randomized_positions if self.randomize_ligand and (phase == 'complex-explicit'): logger.warning("Ligand randomization requested, but will not be performed for explicit solvent simulations.") # Identify whether any atoms will be displaced via MC, unless option is turned off. mc_atoms = None if self.mc_displacement_sigma: mc_atoms = list() if 'ligand' in atom_indices: mc_atoms = atom_indices['ligand'] # Set up simulation. # TODO: Support MPI initialization? logger.debug("Creating replica exchange object...") store_filename = os.path.join(self._store_directory, phase + '.nc') self._store_filenames[phase] = store_filename simulation = ModifiedHamiltonianExchange(store_filename, mpicomm=mpicomm) simulation.create(thermodynamic_state, alchemical_states, positions, displacement_sigma=self.mc_displacement_sigma, mc_atoms=mc_atoms, options=repex_options, metadata=metadata) # Initialize simulation. # TODO: Use the right scheme for initializing the simulation without running. #logger.debug("Initializing simulation...") #simulation.run(0) # TODO: Process user-supplied options. # Clean up simulation. del simulation return
def _create_phase(self, thermodynamic_state, alchemical_phase, restraint_type): """ Create a repex object for a specified phase. Parameters ---------- thermodynamic_state : ThermodynamicState (System need not be defined) Thermodynamic state from which reference temperature and pressure are to be taken. alchemical_phase : AlchemicalPhase The alchemical phase to be created. restraint_type : str or None Restraint type to add between protein and ligand. Supported types are 'FlatBottom' and 'Harmonic'. The second one is available only in implicit solvent. """ # We add default repex options only on creation, on resume repex will pick them from the store file repex_parameters = { 'number_of_equilibration_iterations': 0, 'number_of_iterations': 100, 'timestep': 2.0 * unit.femtoseconds, 'collision_rate': 5.0 / unit.picoseconds, 'minimize': False, 'show_mixing_statistics': True, # this causes slowdown with iteration and should not be used for production 'displacement_sigma': 1.0 * unit.nanometers # attempt to displace ligand by this stddev will be made each iteration } repex_parameters.update(self._repex_parameters) # Convenience variables positions = alchemical_phase.positions reference_system = copy.deepcopy(alchemical_phase.reference_system) atom_indices = alchemical_phase.atom_indices alchemical_states = alchemical_phase.protocol # Check the dimensions of positions. for index in range(len(positions)): n_atoms, _ = (positions[index] / positions[index].unit).shape if n_atoms != reference_system.getNumParticles(): err_msg = "Phase {}: number of atoms in positions {} and and " \ "reference system differ ({} and {} respectively)" err_msg.format(alchemical_phase.name, index, n_atoms, reference_system.getNumParticles()) logger.error(err_msg) raise RuntimeError(err_msg) # Inizialize metadata storage. metadata = dict() # TODO: Use more general approach to determine whether system is periodic. is_periodic = reference_system.usesPeriodicBoundaryConditions() is_complex = len(atom_indices['receptor']) > 0 is_complex_explicit = is_complex and is_periodic is_complex_implicit = is_complex and not is_periodic # Make sure pressure is None if not periodic. if not is_periodic: thermodynamic_state.pressure = None # If temperature and pressure are specified, make sure MonteCarloBarostat is attached. elif thermodynamic_state.temperature and thermodynamic_state.pressure: forces = { reference_system.getForce(index).__class__.__name__ : reference_system.getForce(index) for index in range(reference_system.getNumForces()) } if 'MonteCarloAnisotropicBarostat' in forces: raise Exception('MonteCarloAnisotropicBarostat is unsupported.') if 'MonteCarloBarostat' in forces: logger.debug('MonteCarloBarostat found: Setting default temperature and pressure.') barostat = forces['MonteCarloBarostat'] # Set temperature and pressure. try: barostat.setDefaultTemperature(thermodynamic_state.temperature) except AttributeError: # versions previous to OpenMM7.1 barostat.setTemperature(thermodynamic_state.temperature) barostat.setDefaultPressure(thermodynamic_state.pressure) else: # Create barostat and add it to the system if it doesn't have one already. logger.debug('MonteCarloBarostat not found: Creating one.') barostat = openmm.MonteCarloBarostat(thermodynamic_state.pressure, thermodynamic_state.temperature) reference_system.addForce(barostat) # Store a serialized copy of the reference system. metadata['reference_system'] = openmm.XmlSerializer.serialize(reference_system) metadata['topology'] = utils.serialize_topology(alchemical_phase.reference_topology) # For explicit solvent calculations, we create a copy of the system for # which the fully-interacting energy is to be computed. An enlarged cutoff # is used to account for the anisotropic dispersion correction. This must # be done BEFORE adding the restraint to reference_system. # We dont care about restraint in decoupled state (although we will set to 0) since we will # account for that by hand. # Helper function for expanded cutoff def expand_cutoff(passed_system, max_allowed_cutoff = 16 * unit.angstroms): # Determine minimum box side dimension box_vectors = passed_system.getDefaultPeriodicBoxVectors() min_box_dimension = min([max(vector) for vector in box_vectors]) # Expand cutoff to minimize artifact and verify that box is big enough. # If we use a barostat we leave more room for volume fluctuations or # we risk fatal errors. If we don't use a barostat, OpenMM will raise # the appropriate exception on context creation. max_switching_distance = max_allowed_cutoff - (1 * unit.angstrom) # TODO: Make max_allowed_cutoff an option if thermodynamic_state.pressure and min_box_dimension < 2.25 * max_allowed_cutoff: raise RuntimeError('Barostated box sides must be at least 36 Angstroms ' 'to correct for missing dispersion interactions') logger.debug('Setting cutoff for fully interacting system to maximum ' 'allowed {}'.format(str(max_allowed_cutoff))) # Expanded cutoff system if needed # We don't want to reduce the cutoff if its already large for force in passed_system.getForces(): try: if force.getCutoffDistance() < max_allowed_cutoff: force.setCutoffDistance(max_allowed_cutoff) # Set switch distance # We don't need to check if we are using a switch since there is a setting for that. force.setSwitchingDistance(max_switching_distance) except Exception: pass try: if force.getCutoff() < max_allowed_cutoff: force.setCutoff(max_allowed_cutoff) except Exception: pass # Set the fully-interacting expanded cutoff state here if not is_periodic or not self._anisotropic_dispersion_correction: fully_interacting_expanded_state = None else: # Create the fully interacting system fully_interacting_expanded_system = copy.deepcopy(reference_system) # Expand Cutoff expand_cutoff(fully_interacting_expanded_system) # Construct thermodynamic states fully_interacting_expanded_state = copy.deepcopy(thermodynamic_state) fully_interacting_expanded_state.system = fully_interacting_expanded_system # Compute standard state corrections for complex phase. metadata['standard_state_correction'] = 0.0 # TODO: Do we need to include a standard state correction for other phases in periodic boxes? if is_complex and restraint_type is not None: logger.debug("Creating receptor-ligand restraints...") reference_positions = positions[0] restraints = create_restraints(restraint_type, alchemical_phase.reference_topology, thermodynamic_state, reference_system, reference_positions, atom_indices['receptor'], atom_indices['ligand']) force = restraints.get_restraint_force() # Get Force object incorporating restraints. reference_system.addForce(force) metadata['standard_state_correction'] = restraints.get_standard_state_correction() # in kT elif is_complex_explicit: # For periodic systems, we must still add a standard state # correction for the box volume. # TODO: What if the box volume fluctuates during the simulation? box_vectors = reference_system.getDefaultPeriodicBoxVectors() box_volume = thermodynamic_state._volume(box_vectors) metadata['standard_state_correction'] = - np.log(V0 / box_volume) elif is_complex_implicit: # For implicit solvent/vacuum complex systems, we require a restraint # to keep the ligand from drifting too far away from receptor. raise ValueError('A receptor-ligand system in implicit solvent or ' 'vacuum requires a restraint.') # Create alchemically-modified states using alchemical factory. logger.debug("Creating alchemically-modified states...") try: alchemical_indices = atom_indices['ligand_counterions'] + atom_indices['ligand'] except KeyError: alchemical_indices = atom_indices['ligand'] factory = AbsoluteAlchemicalFactory(reference_system, ligand_atoms=alchemical_indices, **self._alchemy_parameters) alchemical_system = factory.alchemically_modified_system thermodynamic_state.system = alchemical_system # Create the expanded cutoff decoupled state if fully_interacting_expanded_state is None: noninteracting_expanded_state = None else: # Create the system for noninteracting expanded_factory = AbsoluteAlchemicalFactory(fully_interacting_expanded_state.system, ligand_atoms=alchemical_indices, **self._alchemy_parameters) noninteracting_expanded_system = expanded_factory.alchemically_modified_system # Set all USED alchemical interactions to the decoupled state alchemical_state = alchemical_states[-1] AbsoluteAlchemicalFactory.perturbSystem(noninteracting_expanded_system, alchemical_state) # Construct thermodynamic states noninteracting_expanded_state = copy.deepcopy(thermodynamic_state) noninteracting_expanded_state.system = noninteracting_expanded_system # Check systems for finite energies. # TODO: Refactor this into another function. finite_energy_check = False if finite_energy_check: logger.debug("Checking energies are finite for all alchemical systems.") integrator = openmm.VerletIntegrator(1.0 * unit.femtosecond) context = openmm.Context(alchemical_system, integrator) context.setPositions(positions[0]) for index, alchemical_state in enumerate(alchemical_states): AbsoluteAlchemicalFactory.perturbContext(context, alchemical_state) potential = context.getState(getEnergy=True).getPotentialEnergy() if np.isnan(potential / unit.kilocalories_per_mole): raise Exception("Energy for system %d is NaN." % index) del context, integrator logger.debug("All energies are finite.") # Randomize ligand position if requested, but only for implicit solvent systems. if self._randomize_ligand and is_complex_implicit: logger.debug("Randomizing ligand positions and excluding overlapping configurations...") randomized_positions = list() nstates = len(alchemical_states) for state_index in range(nstates): positions_index = np.random.randint(0, len(positions)) current_positions = positions[positions_index] new_positions = ModifiedHamiltonianExchange.randomize_ligand_position(current_positions, atom_indices['receptor'], atom_indices['ligand'], self._randomize_ligand_sigma_multiplier * restraints.getReceptorRadiusOfGyration(), self._randomize_ligand_close_cutoff) randomized_positions.append(new_positions) positions = randomized_positions if self._randomize_ligand and is_complex_explicit: logger.warning("Ligand randomization requested, but will not be performed for explicit solvent simulations.") # Identify whether any atoms will be displaced via MC, unless option is turned off. mc_atoms = None if self._mc_displacement_sigma: mc_atoms = list() if 'ligand' in atom_indices: mc_atoms = atom_indices['ligand'] # Set up simulation. # TODO: Support MPI initialization? logger.debug("Creating replica exchange object...") store_filename = os.path.join(self._store_directory, alchemical_phase.name + '.nc') self._store_filenames[alchemical_phase.name] = store_filename simulation = ModifiedHamiltonianExchange(store_filename, platform=self._platform) simulation.create(thermodynamic_state, alchemical_states, positions, displacement_sigma=self._mc_displacement_sigma, mc_atoms=mc_atoms, options=repex_parameters, metadata=metadata, fully_interacting_expanded_state = fully_interacting_expanded_state, noninteracting_expanded_state = noninteracting_expanded_state) # Initialize simulation. # TODO: Use the right scheme for initializing the simulation without running. #logger.debug("Initializing simulation...") #simulation.run(0) # Clean up simulation. del simulation # Add to list of phases that have been set up. self._phases.append(alchemical_phase.name) self._phases.sort() # sorting ensures all MPI processes run the same phase return
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' nstates = len(systems) #============================================================================== # Run simulation. #============================================================================== # Initialize NetCDF file to store data. import netCDF4 as netcdf ncfile = netcdf.Dataset(filename, 'w', version='NETCDF4') ncfile.createDimension('iteration', 0) # unlimited number of iterations ncfile.createDimension('state', nstates) # number of replicas ncfile.createDimension(
def _propagate_replica(self, replica_index): """ Attempt a Monte Carlo rotation/translation move followed by dynamics. """ # Create and cache Integrator and Context if needed. if not hasattr(self, '_context'): self._cache_context() # 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 cached integrator and context. integrator = self._integrator context = self._context # Set thermodynamic parameters for this state. integrator.setTemperature(state.temperature) integrator.setRandomNumberSeed(int(np.random.randint(0, MAX_SEED))) 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. try: barostat.setDefaultTemperature(state.temperature) except AttributeError: # versions previous to OpenMM0.8 barostat.setTemperature(state.temperature) barostat.setDefaultPressure(state.pressure) context.setParameter(barostat.Pressure(), state.pressure) # must be set in context barostat.setRandomNumberSeed(int(np.random.randint(0, MAX_SEED))) # Set alchemical state. AbsoluteAlchemicalFactory.perturbContext(context, state.alchemical_state) # Set box vectors. box_vectors = self.replica_box_vectors[replica_index] context.setPeriodicBoxVectors(box_vectors[0,:], box_vectors[1,:], box_vectors[2,:]) # Check if initial potential energy is NaN. reduced_potential = state.reduced_potential(self.replica_positions[replica_index], box_vectors=box_vectors, context=context) if np.isnan(reduced_potential): raise Exception('Initial potential for replica %d state %d is NaN before Monte Carlo displacement/rotation' % (replica_index, state_index)) # # Attempt a Monte Carlo rotation/translation move. # # 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, box_vectors=box_vectors, context=context) # 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, box_vectors=box_vectors, context=context) # Accept or reject with Metropolis criteria. du = u_new - u_old if (not np.isnan(u_new)) and ((du <= 0.0) or (np.random.rand() < np.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, box_vectors=box_vectors, context=context) # Compute new potential. perturbed_positions = self.propose_rotation(original_positions, self.mc_atoms) u_new = state.reduced_potential(perturbed_positions, box_vectors=box_vectors, context=context) du = u_new - u_old if (not np.isnan(u_new)) and ((du <= 0.0) or (np.random.rand() < np.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 dynamics. # start_time = time.time() # Run dynamics, retrying if NaNs are encountered. MAX_NAN_RETRIES = 6 nan_counter = 0 completed = False while (not completed): try: # Set box vectors. box_vectors = self.replica_box_vectors[replica_index] context.setPeriodicBoxVectors(box_vectors[0,:], box_vectors[1,:], box_vectors[2,:]) # Check if initial positions are NaN. positions = self.replica_positions[replica_index] if np.any(np.isnan(positions / unit.angstroms)): raise Exception('Initial particle positions for replica %d before propagation are NaN' % replica_index) # Set positions. positions = self.replica_positions[replica_index] context.setPositions(positions) setpositions_end_time = time.time() # Assign Maxwell-Boltzmann velocities. context.setVelocitiesToTemperature(state.temperature, int(np.random.randint(0, MAX_SEED))) setvelocities_end_time = time.time() # Check if initial potential energy is NaN. if np.isnan(context.getState(getEnergy=True).getPotentialEnergy() / state.kT): raise Exception('Potential for replica %d is NaN before dynamics' % replica_index) # Run dynamics. integrator.step(self.nsteps_per_iteration) integrator_end_time = time.time() # Get final positions getstate_start_time = time.time() openmm_state = context.getState(getPositions=True, enforcePeriodicBox=state.system.usesPeriodicBoundaryConditions()) getstate_end_time = time.time() # Check if final positions are NaN. positions = openmm_state.getPositions(asNumpy=True) if np.any(np.isnan(positions / unit.angstroms)): raise Exception('Particle coordinate is nan') # Get box vectors box_vectors = openmm_state.getPeriodicBoxVectors(asNumpy=True) # Check if final potential energy is NaN. if np.isnan(context.getState(getEnergy=True).getPotentialEnergy() / state.kT): raise Exception('Potential for replica %d is NaN after dynamics' % replica_index) # Signal completion completed = True except Exception as e: if str(e) == 'Particle coordinate is nan': # If it's a NaN, increment the NaN counter and try again nan_counter += 1 if nan_counter >= MAX_NAN_RETRIES: raise Exception('Maximum number of NAN retries (%d) exceeded.' % MAX_NAN_RETRIES) logger.info('NaN detected in replica %d. Retrying (%d / %d).' % (replica_index, nan_counter, MAX_NAN_RETRIES)) else: # It's not an exception we recognize, so re-raise it raise e # Store box vectors. self.replica_box_vectors[replica_index] = box_vectors # Store final positions self.replica_positions[replica_index] = positions # Compute timing. end_time = time.time() elapsed_time = end_time - start_time positions_elapsed_time = setpositions_end_time - start_time velocities_elapsed_time = setvelocities_end_time - setpositions_end_time integrator_elapsed_time = integrator_end_time - setvelocities_end_time getstate_elapsed_time = getstate_end_time - integrator_end_time logger.debug("Replica %d/%d: integrator elapsed time %.3f s (positions %.3f s | velocities %.3f s | integrate+getstate %.3f s)." % (replica_index, self.nreplicas, elapsed_time, positions_elapsed_time, velocities_elapsed_time, integrator_elapsed_time+getstate_elapsed_time)) return elapsed_time
def _propagate_replica(self, replica_index): """ Attempt a Monte Carlo rotation/translation move followed by dynamics. """ # Create and cache Integrator and Context if needed. if not hasattr(self, '_context'): self._cache_context() # 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 cached integrator and context. integrator = self._integrator context = self._context # Set thermodynamic parameters for this state. integrator.setTemperature(state.temperature) integrator.setRandomNumberSeed(int(np.random.randint(0, MAX_SEED))) 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) context.setParameter(barostat.Pressure(), state.pressure) # must be set in context barostat.setRandomNumberSeed( int(np.random.randint(0, MAX_SEED))) # Set alchemical state. AbsoluteAlchemicalFactory.perturbContext(context, state.alchemical_state) # # Attempt a Monte Carlo rotation/translation move. # # 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, context=context) # 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, context=context) # Accept or reject with Metropolis criteria. du = u_new - u_old if (du <= 0.0) or (np.random.rand() < np.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, context=context) # Compute new potential. perturbed_positions = self.propose_rotation( original_positions, self.mc_atoms) u_new = state.reduced_potential(perturbed_positions, context=context) du = u_new - u_old if (du <= 0.0) or (np.random.rand() < np.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 dynamics. # start_time = time.time() # Set box vectors. box_vectors = self.replica_box_vectors[replica_index] context.setPeriodicBoxVectors(box_vectors[0, :], box_vectors[1, :], box_vectors[2, :]) # Set positions. positions = self.replica_positions[replica_index] context.setPositions(positions) setpositions_end_time = time.time() # Assign Maxwell-Boltzmann velocities. context.setVelocitiesToTemperature(state.temperature, int(np.random.randint(0, MAX_SEED))) setvelocities_end_time = time.time() # Run dynamics. integrator.step(self.nsteps_per_iteration) integrator_end_time = time.time() # Store final positions getstate_start_time = time.time() openmm_state = context.getState(getPositions=True) getstate_end_time = time.time() self.replica_positions[replica_index] = openmm_state.getPositions( asNumpy=True) # Store box vectors. self.replica_box_vectors[ replica_index] = openmm_state.getPeriodicBoxVectors(asNumpy=True) # Compute timing. end_time = time.time() elapsed_time = end_time - start_time positions_elapsed_time = setpositions_end_time - start_time velocities_elapsed_time = setvelocities_end_time - setpositions_end_time integrator_elapsed_time = integrator_end_time - setvelocities_end_time getstate_elapsed_time = getstate_end_time - integrator_end_time logger.debug( "Replica %d/%d: integrator elapsed time %.3f s (positions %.3f s | velocities %.3f s | integrate+getstate %.3f s)." % (replica_index, self.nreplicas, elapsed_time, positions_elapsed_time, velocities_elapsed_time, integrator_elapsed_time + getstate_elapsed_time)) return elapsed_time
def _create_phase(self, phase, reference_system, positions, atom_indices, thermodynamic_state, protocols=None): """ Create a repex object for a specified phase. Parameters ---------- phase : str The phase being initialized (one of ['complex', 'solvent', 'vacuum']) reference_system : simtk.openmm.System The reference system object from which alchemical intermediates are to be construcfted. positions : list of simtk.unit.Qunatity objects containing (natoms x 3) positions (as np or lists) The list of positions to be used to seed replicas in a round-robin way. atom_indices : dict atom_indices[phase][component] is the set of atom indices associated with component, where component is ['ligand', 'receptor', 'complex', 'solvent', 'ligand_counterions'] thermodynamic_state : ThermodynamicState Thermodynamic state from which reference temperature and pressure are to be taken. protocols : dict of list of AlchemicalState, optional, default=None If specified, the alchemical protocol protocols[phase] will be used for phase 'phase' instead of the default. """ # We add default repex options only on creation, on resume repex will pick them from the store file repex_parameters = { 'number_of_equilibration_iterations': 0, 'number_of_iterations': 100, 'timestep': 2.0 * unit.femtoseconds, 'collision_rate': 5.0 / unit.picoseconds, 'minimize': False, 'show_mixing_statistics': True, # this causes slowdown with iteration and should not be used for production 'displacement_sigma': 1.0 * unit.nanometers # attempt to displace ligand by this stddev will be made each iteration } repex_parameters.update(self._repex_parameters) # Make sure positions argument is a list of coordinate snapshots. if hasattr(positions, 'unit'): # Wrap in list. positions = [positions] # Check the dimensions of positions. for index in range(len(positions)): # Make sure it is recast as a np array. positions[index] = unit.Quantity(np.array(positions[index] / positions[index].unit), positions[index].unit) [natoms, ndim] = (positions[index] / positions[index].unit).shape if natoms != reference_system.getNumParticles(): raise Exception("positions argument must be a list of simtk.unit.Quantity of (natoms,3) lists or np array with units compatible with nanometers.") # Create metadata storage. metadata = dict() # Make a deep copy of the reference system so we don't accidentally modify it. reference_system = copy.deepcopy(reference_system) # TODO: Use more general approach to determine whether system is periodic. is_periodic = self._is_periodic(reference_system) # Make sure pressure is None if not periodic. if not is_periodic: thermodynamic_state.pressure = None # Compute standard state corrections for complex phase. metadata['standard_state_correction'] = 0.0 # TODO: Do we need to include a standard state correction for other phases in periodic boxes? if phase == 'complex-implicit': # Impose restraints for complex system in implicit solvent to keep ligand from drifting too far away from receptor. logger.debug("Creating receptor-ligand restraints...") reference_positions = positions[0] if self._restraint_type == 'harmonic': restraints = HarmonicReceptorLigandRestraint(thermodynamic_state, reference_system, reference_positions, atom_indices['receptor'], atom_indices['ligand']) elif self._restraint_type == 'flat-bottom': restraints = FlatBottomReceptorLigandRestraint(thermodynamic_state, reference_system, reference_positions, atom_indices['receptor'], atom_indices['ligand']) else: raise Exception("restraint_type of '%s' is not supported." % self._restraint_type) force = restraints.getRestraintForce() # Get Force object incorporating restraints reference_system.addForce(force) metadata['standard_state_correction'] = restraints.getStandardStateCorrection() # standard state correction in kT elif phase == 'complex-explicit': # For periodic systems, we do not use a restraint, but must add a standard state correction for the box volume. # TODO: What if the box volume fluctuates during the simulation? box_vectors = reference_system.getDefaultPeriodicBoxVectors() box_volume = thermodynamic_state._volume(box_vectors) STANDARD_STATE_VOLUME = 1660.53928 * unit.angstrom**3 metadata['standard_state_correction'] = - np.log(STANDARD_STATE_VOLUME / box_volume) # Use default alchemical protocols if not specified. if not protocols: protocols = self.default_protocols # Create alchemically-modified states using alchemical factory. logger.debug("Creating alchemically-modified states...") try: alchemical_indices = atom_indices['ligand_counterions'] + atom_indices['ligand'] except KeyError: alchemical_indices = atom_indices['ligand'] factory = AbsoluteAlchemicalFactory(reference_system, ligand_atoms=alchemical_indices, **self._alchemy_parameters) alchemical_states = protocols[phase] alchemical_system = factory.alchemically_modified_system thermodynamic_state.system = alchemical_system # Check systems for finite energies. # TODO: Refactor this into another function. finite_energy_check = False if finite_energy_check: logger.debug("Checking energies are finite for all alchemical systems.") integrator = openmm.VerletIntegrator(1.0 * unit.femtosecond) context = openmm.Context(alchemical_system, integrator) context.setPositions(positions[0]) for alchemical_state in alchemical_states: AbsoluteAlchemicalFactory.perturbContext(context, alchemical_state) potential = context.getState(getEnergy=True).getPotentialEnergy() if np.isnan(potential / unit.kilocalories_per_mole): raise Exception("Energy for system %d is NaN." % index) del context, integrator logger.debug("All energies are finite.") # Randomize ligand position if requested, but only for implicit solvent systems. if self._randomize_ligand and (phase == 'complex-implicit'): logger.debug("Randomizing ligand positions and excluding overlapping configurations...") randomized_positions = list() nstates = len(alchemical_states) for state_index in range(nstates): positions_index = np.random.randint(0, len(positions)) current_positions = positions[positions_index] new_positions = ModifiedHamiltonianExchange.randomize_ligand_position(current_positions, atom_indices['receptor'], atom_indices['ligand'], self._randomize_ligand_sigma_multiplier * restraints.getReceptorRadiusOfGyration(), self._randomize_ligand_close_cutoff) randomized_positions.append(new_positions) positions = randomized_positions if self._randomize_ligand and (phase == 'complex-explicit'): logger.warning("Ligand randomization requested, but will not be performed for explicit solvent simulations.") # Identify whether any atoms will be displaced via MC, unless option is turned off. mc_atoms = None if self._mc_displacement_sigma: mc_atoms = list() if 'ligand' in atom_indices: mc_atoms = atom_indices['ligand'] # Set up simulation. # TODO: Support MPI initialization? logger.debug("Creating replica exchange object...") store_filename = os.path.join(self._store_directory, phase + '.nc') self._store_filenames[phase] = store_filename simulation = ModifiedHamiltonianExchange(store_filename) simulation.create(thermodynamic_state, alchemical_states, positions, displacement_sigma=self._mc_displacement_sigma, mc_atoms=mc_atoms, options=repex_parameters, metadata=metadata) # Initialize simulation. # TODO: Use the right scheme for initializing the simulation without running. #logger.debug("Initializing simulation...") #simulation.run(0) # Clean up simulation. del simulation # Add to list of phases that have been set up. self._phases.append(phase) return