Example #1
0
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
Example #2
0
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))
Example #3
0
    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
Example #4
0
    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
Example #5
0
    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
Example #6
0
    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
Example #7
0
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'])
Example #8
0
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
Example #9
0
    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]
Example #10
0
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
Example #11
0
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))
Example #12
0
    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
Example #13
0
    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
Example #14
0
    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
Example #15
0
    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
Example #16
0
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
Example #17
0
    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
Example #18
0
 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
Example #19
0
 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
Example #20
0
    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
Example #21
0
    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
Example #22
0
    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."
Example #24
0
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
Example #25
0
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
Example #26
0
    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()
Example #27
0
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:
Example #29
0
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))
Example #30
0
    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
Example #31
0
    # 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,
Example #32
0
    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)
Example #35
0
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))
Example #36
0
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
Example #37
0
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
Example #38
0
    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())))
Example #39
0
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
Example #40
0
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)
Example #41
0
    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
Example #42
0
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
Example #43
0
    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
Example #44
0
    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
Example #45
0
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(
Example #46
0
    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
Example #47
0
    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
Example #48
0
    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