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)
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)
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
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()
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()
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()