Example #1
0
    def _get_options_dictionary(self, available_resources):
        """Returns a dictionary of options which will be serialized
        to a yaml file and passed to YANK.

        Parameters
        ----------
        available_resources: ComputeResources
            The resources available to execute on.

        Returns
        -------
        dict of str and Any
            A yaml compatible dictionary of YANK options.
        """

        from openforcefield.utils import quantity_to_string

        platform_name = 'CPU'

        if available_resources.number_of_gpus > 0:

            # A platform which runs on GPUs has been requested.
            from propertyestimator.backends import ComputeResources
            toolkit_enum = ComputeResources.GPUToolkit(
                available_resources.preferred_gpu_toolkit)

            # A platform which runs on GPUs has been requested.
            platform_name = 'CUDA' if toolkit_enum == ComputeResources.GPUToolkit.CUDA else \
                                                      ComputeResources.GPUToolkit.OpenCL

        return {
            'verbose':
            self.verbose,
            'output_dir':
            '.',
            'temperature':
            quantity_to_string(
                pint_quantity_to_openmm(self.thermodynamic_state.temperature)),
            'pressure':
            quantity_to_string(
                pint_quantity_to_openmm(self.thermodynamic_state.pressure)),
            'minimize':
            True,
            'number_of_equilibration_iterations':
            self.number_of_equilibration_iterations,
            'default_number_of_iterations':
            self.number_of_iterations,
            'default_nsteps_per_iteration':
            self.steps_per_iteration,
            'checkpoint_interval':
            self.checkpoint_interval,
            'default_timestep':
            quantity_to_string(pint_quantity_to_openmm(self.timestep)),
            'annihilate_electrostatics':
            True,
            'annihilate_sterics':
            False,
            'platform':
            platform_name
        }
def test_pint_to_openmm(pint_unit, value):

    pint_quantity = value * pint_unit
    pint_raw_value = pint_quantity.magnitude

    openmm_quantity = pint_quantity_to_openmm(pint_quantity)
    openmm_raw_value = openmm_quantity.value_in_unit(openmm_quantity.unit)

    assert np.allclose(openmm_raw_value, pint_raw_value)
Example #3
0
    def execute(self, directory, available_resources):

        logging.info('Minimising energy: ' + self.id)

        platform = setup_platform_with_resources(available_resources)

        input_pdb_file = app.PDBFile(self.input_coordinate_file)

        with open(self.system_path, 'rb') as file:
            system = openmm.XmlSerializer.deserialize(file.read().decode())

        if not self.enable_pbc:

            for force_index in range(system.getNumForces()):

                force = system.getForce(force_index)

                if not isinstance(force, openmm.NonbondedForce):
                    continue

                force.setNonbondedMethod(
                    0)  # NoCutoff = 0, NonbondedMethod.CutoffNonPeriodic = 1

        # TODO: Expose the constraint tolerance
        integrator = openmm.VerletIntegrator(0.002 * simtk_unit.picoseconds)
        simulation = app.Simulation(input_pdb_file.topology, system,
                                    integrator, platform)

        box_vectors = input_pdb_file.topology.getPeriodicBoxVectors()

        if box_vectors is None:
            box_vectors = simulation.system.getDefaultPeriodicBoxVectors()

        simulation.context.setPeriodicBoxVectors(*box_vectors)
        simulation.context.setPositions(input_pdb_file.positions)

        simulation.minimizeEnergy(pint_quantity_to_openmm(self.tolerance),
                                  self.max_iterations)

        positions = simulation.context.getState(
            getPositions=True).getPositions()

        self.output_coordinate_file = os.path.join(directory, 'minimised.pdb')

        with open(self.output_coordinate_file, 'w+') as minimised_file:
            app.PDBFile.writeFile(simulation.topology, positions,
                                  minimised_file)

        logging.info('Energy minimised: ' + self.id)

        return self._get_output_dictionary()
def test_combinatorial_pint_to_openmm():

    all_pint_units = _get_all_pint_units()

    for i in range(len(all_pint_units)):

        for j in range(i, len(all_pint_units)):

            pint_unit = all_pint_units[i] * all_pint_units[j]

            pint_quantity = random() * pint_unit
            pint_raw_value = pint_quantity.magnitude

            openmm_quantity = pint_quantity_to_openmm(pint_quantity)
            openmm_raw_value = openmm_quantity.value_in_unit(
                openmm_quantity.unit)

            assert np.isclose(openmm_raw_value, pint_raw_value)
def test_pint_unit_conversions(pint_unit, value):

    pint_quantity = value * pint_unit

    pint_base_quantity = pint_quantity.to_base_units()
    openmm_base_quantity = pint_quantity_to_openmm(pint_base_quantity)

    openmm_unit = pint_unit_to_openmm(pint_unit)
    openmm_quantity = openmm_base_quantity.in_units_of(openmm_unit)

    pint_raw_value = pint_quantity.magnitude

    if pint_unit == unit.dimensionless and (
            isinstance(openmm_quantity, float) or isinstance(
                openmm_quantity, int) or isinstance(openmm_quantity, list)
            or isinstance(openmm_quantity, np.ndarray)):
        openmm_raw_value = openmm_quantity
    else:
        openmm_raw_value = openmm_quantity.value_in_unit(openmm_unit)

    assert np.allclose(openmm_raw_value, pint_raw_value)
    def _evaluate_reduced_potential(self, system, trajectory, file_path,
                                    compute_resources, subset_energy_corrections=None):
        """Return the potential energy.
        Parameters
        ----------
        system: simtk.openmm.System
            The system which encodes the interaction forces for the
            specified parameter.
        trajectory: mdtraj.Trajectory
            A trajectory of configurations to evaluate.
        file_path: str
            The path to save the reduced potentials to.
        compute_resources: ComputeResources
            The compute resources available to execute on.
        subset_energy_corrections: unit.Quantity, optional
            A unit.Quantity wrapped numpy.ndarray which contains a set
            of energies to add to the re-evaluated potential energies.
            This is mainly used to correct the potential energies evaluated
            using a subset of the force field back to energies as if evaluated
            using the full thing.

        Returns
        ---------
        propertyestimator.unit.Quantity
            A unit bearing `np.ndarray` which contains the reduced potential.
        PropertyEstimatorException, optional
            Any exceptions that were raised.
        """
        from simtk import unit as simtk_unit

        integrator = openmm.VerletIntegrator(0.1 * simtk_unit.femtoseconds)

        platform = setup_platform_with_resources(compute_resources, True)
        openmm_context = openmm.Context(system, integrator, platform)

        potentials = np.zeros(trajectory.n_frames, dtype=np.float64)
        reduced_potentials = np.zeros(trajectory.n_frames, dtype=np.float64)

        temperature = pint_quantity_to_openmm(self.thermodynamic_state.temperature)
        beta = 1.0 / (simtk_unit.BOLTZMANN_CONSTANT_kB * temperature)

        pressure = pint_quantity_to_openmm(self.thermodynamic_state.pressure)

        for frame_index in range(trajectory.n_frames):

            positions = trajectory.xyz[frame_index]
            box_vectors = trajectory.openmm_boxes(frame_index)

            if self.enable_pbc:
                openmm_context.setPeriodicBoxVectors(*box_vectors)

            openmm_context.setPositions(positions)

            state = openmm_context.getState(getEnergy=True)

            unreduced_potential = state.getPotentialEnergy() / simtk_unit.AVOGADRO_CONSTANT_NA

            if pressure is not None and self.enable_pbc:
                unreduced_potential += pressure * state.getPeriodicBoxVolume()

            potentials[frame_index] = state.getPotentialEnergy().value_in_unit(simtk_unit.kilojoule_per_mole)
            reduced_potentials[frame_index] = unreduced_potential * beta

        potentials *= unit.kilojoule / unit.mole
        reduced_potentials *= unit.dimensionless

        if subset_energy_corrections is not None:
            potentials += subset_energy_corrections

        statistics_array = StatisticsArray()
        statistics_array[ObservableType.ReducedPotential] = reduced_potentials
        statistics_array[ObservableType.PotentialEnergy] = potentials
        statistics_array.to_pandas_csv(file_path)
Example #7
0
    def _setup_simulation_objects(self, temperature, pressure,
                                  available_resources):
        """Initializes the objects needed to perform the simulation.
        This comprises of a context, and an integrator.

        Parameters
        ----------
        temperature: simtk.unit.Quantity
            The temperature to run the simulation at.
        pressure: simtk.unit.Quantity
            The pressure to run the simulation at.
        available_resources: ComputeResources
            The resources available to run on.

        Returns
        -------
        simtk.openmm.Context
            The created openmm context which takes advantage
            of the available compute resources.
        openmmtools.integrators.LangevinIntegrator
            The Langevin integrator which will propogate
            the simulation.
        """

        import openmmtools
        from simtk.openmm import XmlSerializer

        # Create a platform with the correct resources.
        if not self.allow_gpu_platforms:

            from propertyestimator.backends import ComputeResources
            available_resources = ComputeResources(
                available_resources.number_of_threads)

        platform = setup_platform_with_resources(available_resources,
                                                 self.high_precision)

        # Load in the system object from the provided xml file.
        with open(self.system_path, 'r') as file:
            system = XmlSerializer.deserialize(file.read())

        # Disable the periodic boundary conditions if requested.
        if not self.enable_pbc:

            disable_pbc(system)
            pressure = None

        # Use the openmmtools ThermodynamicState object to help
        # set up a system which contains the correct barostat if
        # one should be present.
        openmm_state = openmmtools.states.ThermodynamicState(
            system=system, temperature=temperature, pressure=pressure)

        system = openmm_state.get_system(remove_thermostat=True)

        # Set up the integrator.
        thermostat_friction = pint_quantity_to_openmm(self.thermostat_friction)
        timestep = pint_quantity_to_openmm(self.timestep)

        integrator = openmmtools.integrators.LangevinIntegrator(
            temperature=temperature,
            collision_rate=thermostat_friction,
            timestep=timestep)

        # Create the simulation context.
        context = openmm.Context(system, integrator, platform)

        # Initialize the context with the correct positions etc.
        input_pdb_file = app.PDBFile(self.input_coordinate_file)

        if self.enable_pbc:

            # Optionally set up the box vectors.
            box_vectors = input_pdb_file.topology.getPeriodicBoxVectors()

            if box_vectors is None:

                raise ValueError('The input file must contain box vectors '
                                 'when running with PBC.')

            context.setPeriodicBoxVectors(*box_vectors)

        context.setPositions(input_pdb_file.positions)
        context.setVelocitiesToTemperature(temperature)

        return context, integrator
Example #8
0
    def execute(self, directory, available_resources):

        # We handle most things in OMM units here.
        temperature = self.thermodynamic_state.temperature
        openmm_temperature = pint_quantity_to_openmm(temperature)

        pressure = None if self.ensemble == Ensemble.NVT else self.thermodynamic_state.pressure
        openmm_pressure = pint_quantity_to_openmm(pressure)

        if openmm_temperature is None:

            return PropertyEstimatorException(
                directory=directory,
                message='A temperature must be set to perform '
                'a simulation in any ensemble')

        if Ensemble(self.ensemble) == Ensemble.NPT and openmm_pressure is None:

            return PropertyEstimatorException(
                directory=directory,
                message='A pressure must be set to perform an NPT simulation')

        if Ensemble(
                self.ensemble) == Ensemble.NPT and self.enable_pbc is False:

            return PropertyEstimatorException(
                directory=directory,
                message='PBC must be enabled when running in the NPT ensemble.'
            )

        logging.info('Performing a simulation in the ' + str(self.ensemble) +
                     ' ensemble: ' + self.id)

        # Set up the internal file paths
        self._checkpoint_path = os.path.join(directory, 'checkpoint.json')
        self._state_path = os.path.join(directory, 'checkpoint_state.xml')

        self._local_trajectory_path = os.path.join(directory, 'trajectory.dcd')
        self._local_statistics_path = os.path.join(directory,
                                                   'openmm_statistics.csv')

        # Set up the simulation objects.
        if self._context is None or self._integrator is None:

            self._context, self._integrator = self._setup_simulation_objects(
                openmm_temperature, openmm_pressure, available_resources)

        # Save a copy of the starting configuration if it doesn't already exist
        local_input_coordinate_path = os.path.join(directory, 'input.pdb')

        if not os.path.isfile(local_input_coordinate_path):

            input_pdb_file = app.PDBFile(self.input_coordinate_file)

            with open(local_input_coordinate_path, 'w+') as configuration_file:
                app.PDBFile.writeFile(input_pdb_file.topology,
                                      input_pdb_file.positions,
                                      configuration_file)

        # Run the simulation.
        result = self._simulate(directory, self._context, self._integrator)

        if isinstance(result, PropertyEstimatorException):
            return result

        # Set the output paths.
        self.trajectory_file_path = self._local_trajectory_path
        self.statistics_file_path = os.path.join(directory, 'statistics.csv')

        # Save out the final statistics in the property estimator format
        self._save_final_statistics(self.statistics_file_path, temperature,
                                    pressure)

        return self._get_output_dictionary()
Example #9
0
    def execute(self, directory, available_resources):

        import mdtraj
        from openforcefield.topology import Molecule, Topology

        logging.info(
            f'Generating a system with LigParGen for {self.substance.identifier}: {self._id}'
        )

        with open(self.force_field_path) as file:
            force_field_source = ForceFieldSource.parse_json(file.read())

        if not isinstance(force_field_source, LigParGenForceFieldSource):

            return PropertyEstimatorException(
                directory=directory,
                message=
                'Only LigParGen force field sources are supported by this '
                'protocol.')

        # Load in the systems coordinates / topology
        openmm_pdb_file = app.PDBFile(self.coordinate_file_path)

        # Create an OFF topology for better insight into the layout of the system topology.
        unique_molecules = [
            Molecule.from_smiles(component.smiles)
            for component in self.substance.components
        ]

        # Create a dictionary of representative topology molecules for each component.
        topology = Topology.from_openmm(openmm_pdb_file.topology,
                                        unique_molecules)

        # Create the template system objects for each component in the system.
        system_templates = {}

        cutoff = pint_quantity_to_openmm(force_field_source.cutoff)

        for index, component in enumerate(self.substance.components):

            reference_topology_molecule = None

            # Create temporary pdb files for each molecule type in the system, with their constituent
            # atoms ordered in the same way that they would be in the full system.
            topology_molecule = None

            for topology_molecule in topology.topology_molecules:

                if topology_molecule.reference_molecule.to_smiles(
                ) != unique_molecules[index].to_smiles():
                    continue

                reference_topology_molecule = topology_molecule
                break

            if reference_topology_molecule is None or topology_molecule is None:
                return PropertyEstimatorException(
                    'A topology molecule could not be matched to its reference.'
                )

            # Create the force field template using the LigParGen server.
            if component.smiles != 'O' and component.smiles != '[H]O[H]':

                force_field_path = self._parameterize_smiles(
                    component.smiles, force_field_source, directory)

                start_index = reference_topology_molecule.atom_start_topology_index
                end_index = start_index + reference_topology_molecule.n_atoms
                index_range = list(range(start_index, end_index))

                component_pdb_file = mdtraj.load_pdb(self.coordinate_file_path,
                                                     atom_indices=index_range)
                component_topology = component_pdb_file.topology.to_openmm()
                component_topology.setUnitCellDimensions(
                    openmm_pdb_file.topology.getUnitCellDimensions())

                # Create the system object.
                # noinspection PyTypeChecker
                force_field_template = app.ForceField(force_field_path)

                component_system = force_field_template.createSystem(
                    topology=component_topology,
                    nonbondedMethod=app.PME,
                    nonbondedCutoff=cutoff,
                    constraints=app.HBonds,
                    rigidWater=True,
                    removeCMMotion=False)
            else:

                component_system = self._build_tip3p_system(
                    topology_molecule, cutoff,
                    openmm_pdb_file.topology.getUnitCellDimensions())

            system_templates[
                unique_molecules[index].to_smiles()] = component_system

        # Create the full system object from the component templates.
        system = None

        for topology_molecule in topology.topology_molecules:

            system_template = system_templates[
                topology_molecule.reference_molecule.to_smiles()]

            if system is None:

                # If no system has been set up yet, just use the first template.
                system = copy.deepcopy(system_template)
                continue

            # Append the component template to the full system.
            self._append_system(system, system_template)

        # Apply the OPLS mixing rules.
        self._apply_opls_mixing_rules(system)

        # Serialize the system object.
        system_xml = openmm.XmlSerializer.serialize(system)

        self.system_path = os.path.join(directory, 'system.xml')

        with open(self.system_path, 'wb') as file:
            file.write(system_xml.encode('utf-8'))

        logging.info(f'System generated: {self.id}')

        return self._get_output_dictionary()
Example #10
0
    def execute(self, directory, available_resources):

        from openforcefield.topology import Molecule, Topology

        logging.info(
            f'Generating a system with tleap for {self.substance.identifier}: {self._id}'
        )

        with open(self.force_field_path) as file:
            force_field_source = ForceFieldSource.parse_json(file.read())

        if not isinstance(force_field_source, TLeapForceFieldSource):

            return PropertyEstimatorException(
                directory=directory,
                message='Only TLeap force field sources are supported by this '
                'protocol.')

        # Load in the systems coordinates / topology
        openmm_pdb_file = app.PDBFile(self.coordinate_file_path)

        # Create an OFF topology for better insight into the layout of the system topology.
        unique_molecules = [
            Molecule.from_smiles(component.smiles)
            for component in self.substance.components
        ]

        topology = Topology.from_openmm(openmm_pdb_file.topology,
                                        unique_molecules)

        # Find a unique instance of each topology molecule to get the correct
        # atom orderings.
        topology_molecules = dict()

        for topology_molecule in topology.topology_molecules:
            topology_molecules[topology_molecule.reference_molecule.to_smiles(
            )] = topology_molecule

        system_templates = {}

        cutoff = pint_quantity_to_openmm(force_field_source.cutoff)

        for index, (smiles, topology_molecule) in enumerate(
                topology_molecules.items()):

            component_directory = os.path.join(directory, str(index))

            if os.path.isdir(component_directory):
                shutil.rmtree(component_directory)

            os.makedirs(component_directory, exist_ok=True)

            if smiles != 'O' and smiles != '[H]O[H]':

                initial_mol2_name = 'initial.mol2'
                initial_mol2_path = os.path.join(component_directory,
                                                 initial_mol2_name)

                self._topology_molecule_to_mol2(topology_molecule,
                                                initial_mol2_path,
                                                self.charge_backend)
                prmtop_path, _, error = self._run_tleap(
                    force_field_source, initial_mol2_name, component_directory)

                if error is not None:
                    return error

                prmtop_file = openmm.app.AmberPrmtopFile(prmtop_path)

                component_system = prmtop_file.createSystem(
                    nonbondedMethod=app.PME,
                    nonbondedCutoff=cutoff,
                    constraints=app.HBonds,
                    rigidWater=True,
                    removeCMMotion=False)

                if openmm_pdb_file.topology.getPeriodicBoxVectors(
                ) is not None:
                    component_system.setDefaultPeriodicBoxVectors(
                        *openmm_pdb_file.topology.getPeriodicBoxVectors())
            else:

                component_system = self._build_tip3p_system(
                    topology_molecule, cutoff,
                    openmm_pdb_file.topology.getUnitCellDimensions())

            system_templates[
                unique_molecules[index].to_smiles()] = component_system

            with open(os.path.join(component_directory, f'component.xml'),
                      'w') as file:
                file.write(openmm.XmlSerializer.serialize(component_system))

        # Create the full system object from the component templates.
        system = None

        for topology_molecule in topology.topology_molecules:

            system_template = system_templates[
                topology_molecule.reference_molecule.to_smiles()]

            if system is None:

                # If no system has been set up yet, just use the first template.
                system = copy.deepcopy(system_template)
                continue

            # Append the component template to the full system.
            self._append_system(system, system_template)

        # Serialize the system object.
        system_xml = openmm.XmlSerializer.serialize(system)

        self.system_path = os.path.join(directory, 'system.xml')

        with open(self.system_path, 'w') as file:
            file.write(system_xml)

        logging.info(f'System generated: {self.id}')

        return self._get_output_dictionary()
    def execute(self, directory, available_resources):

        import openmmtools
        import mdtraj

        from simtk import openmm, unit as simtk_unit
        from simtk.openmm import XmlSerializer

        trajectory = mdtraj.load_dcd(self.trajectory_file_path, self.coordinate_file_path)

        with open(self.system_path, 'rb') as file:
            system = XmlSerializer.deserialize(file.read().decode())

        temperature = pint_quantity_to_openmm(self.thermodynamic_state.temperature)
        pressure = pint_quantity_to_openmm(self.thermodynamic_state.pressure)

        if self.enable_pbc:
            system.setDefaultPeriodicBoxVectors(*trajectory.openmm_boxes(0))
        else:
            pressure = None

        openmm_state = openmmtools.states.ThermodynamicState(system=system,
                                                             temperature=temperature,
                                                             pressure=pressure)

        integrator = openmmtools.integrators.VelocityVerletIntegrator(0.01*simtk_unit.femtoseconds)

        # Setup the requested platform:
        platform = setup_platform_with_resources(available_resources, self.high_precision)
        openmm_system = openmm_state.get_system(True, True)

        if not self.enable_pbc:
            disable_pbc(openmm_system)

        openmm_context = openmm.Context(openmm_system, integrator, platform)

        potential_energies = np.zeros(trajectory.n_frames)
        reduced_potentials = np.zeros(trajectory.n_frames)

        for frame_index in range(trajectory.n_frames):

            if self.enable_pbc:
                box_vectors = trajectory.openmm_boxes(frame_index)
                openmm_context.setPeriodicBoxVectors(*box_vectors)

            positions = trajectory.xyz[frame_index]
            openmm_context.setPositions(positions)

            potential_energy = openmm_context.getState(getEnergy=True).getPotentialEnergy()

            potential_energies[frame_index] = potential_energy.value_in_unit(simtk_unit.kilojoule_per_mole)
            reduced_potentials[frame_index] = openmm_state.reduced_potential(openmm_context)

        kinetic_energies = StatisticsArray.from_pandas_csv(self.kinetic_energies_path)[ObservableType.KineticEnergy]

        statistics_array = StatisticsArray()
        statistics_array[ObservableType.PotentialEnergy] = potential_energies * unit.kilojoule / unit.mole
        statistics_array[ObservableType.KineticEnergy] = kinetic_energies
        statistics_array[ObservableType.ReducedPotential] = reduced_potentials * unit.dimensionless

        statistics_array[ObservableType.TotalEnergy] = (statistics_array[ObservableType.PotentialEnergy] +
                                                        statistics_array[ObservableType.KineticEnergy])

        statistics_array[ObservableType.Enthalpy] = (statistics_array[ObservableType.ReducedPotential] *
                                                     self.thermodynamic_state.inverse_beta + kinetic_energies)

        if self.use_internal_energy:
            statistics_array[ObservableType.ReducedPotential] += kinetic_energies * self.thermodynamic_state.beta

        self.statistics_file_path = path.join(directory, 'statistics.csv')
        statistics_array.to_pandas_csv(self.statistics_file_path)

        return self._get_output_dictionary()