def _execute(self, directory, available_resources): from simtk.openmm import XmlSerializer solute_components = [ component for component in self.solute.components if component.role == Component.Role.Solute ] solvent_1_components = [ component for component in self.solvent_1.components if component.role == Component.Role.Solvent ] solvent_2_components = [ component for component in self.solvent_2.components if component.role == Component.Role.Solvent ] if len(solute_components) != 1: raise ValueError( "There must only be a single component marked as a solute.") if len(solvent_1_components) == 0 and len(solvent_2_components) == 0: raise ValueError( "At least one of the solvents must not be vacuum.") # Because of quirks in where Yank looks files while doing temporary # directory changes, we need to copy the coordinate files locally so # they are correctly found. shutil.copyfile( self.solvent_1_coordinates, os.path.join(directory, self._local_solvent_1_coordinates), ) shutil.copyfile(self.solvent_1_system, os.path.join(directory, self._local_solvent_1_system)) shutil.copyfile( self.solvent_2_coordinates, os.path.join(directory, self._local_solvent_2_coordinates), ) shutil.copyfile(self.solvent_2_system, os.path.join(directory, self._local_solvent_2_system)) # Disable the pbc of the any solvents which should be treated # as vacuum. vacuum_system_path = None if len(solvent_1_components) == 0: vacuum_system_path = self._local_solvent_1_system elif len(solvent_2_components) == 0: vacuum_system_path = self._local_solvent_2_system if vacuum_system_path is not None: logger.info( f"Disabling the periodic boundary conditions in {vacuum_system_path} " f"by setting the cutoff type to NoCutoff") with open(os.path.join(directory, vacuum_system_path), "r") as file: vacuum_system = XmlSerializer.deserialize(file.read()) disable_pbc(vacuum_system) with open(os.path.join(directory, vacuum_system_path), "w") as file: file.write(XmlSerializer.serialize(vacuum_system)) # Set up the yank input file. super(SolvationYankProtocol, self)._execute(directory, available_resources) if self.setup_only: return solvent_1_yank_path = os.path.join(directory, "experiments", "solvent1.nc") solvent_2_yank_path = os.path.join(directory, "experiments", "solvent2.nc") self.solvent_1_trajectory_path = os.path.join(directory, "solvent1.dcd") self.solvent_2_trajectory_path = os.path.join(directory, "solvent2.dcd") self._extract_trajectory(solvent_1_yank_path, self.solvent_1_trajectory_path) self._extract_trajectory(solvent_2_yank_path, self.solvent_2_trajectory_path)
def _execute(self, directory, available_resources): import openmmtools import mdtraj trajectory = mdtraj.load_dcd(self.trajectory_file_path, self.coordinate_file_path) with open(self.system_path, "r") as file: system = openmm.XmlSerializer.deserialize(file.read()) 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 = os.path.join(directory, "statistics.csv") statistics_array.to_pandas_csv(self.statistics_file_path)
def _build_reduced_system(self, original_force_field, topology, scale_amount=None): """Produces an OpenMM system containing only forces for the specified parameter, optionally perturbed by the amount specified by `scale_amount`. Parameters ---------- original_force_field: openforcefield.typing.engines.smirnoff.ForceField The force field to create the system from (and optionally perturb). topology: openforcefield.topology.Topology The topology of the system to apply the force field to. scale_amount: float, optional The optional amount to perturb the parameter by. Returns ------- simtk.openmm.System The created system. simtk.pint.Quantity The new value of the perturbed parameter. """ # As this method deals mainly with the toolkit, we stick to # simtk units here. from openforcefield.typing.engines.smirnoff import ForceField parameter_tag = self.parameter_key.tag parameter_smirks = self.parameter_key.smirks parameter_attribute = self.parameter_key.attribute original_handler = original_force_field.get_parameter_handler( parameter_tag) original_parameter = original_handler.parameters[parameter_smirks] if self.use_subset_of_force_field: force_field = ForceField() handler = copy.deepcopy( original_force_field.get_parameter_handler(parameter_tag)) force_field.register_parameter_handler(handler) else: force_field = copy.deepcopy(original_force_field) handler = force_field.get_parameter_handler(parameter_tag) parameter_index = None value_list = None if hasattr(original_parameter, parameter_attribute): parameter_value = getattr(original_parameter, parameter_attribute) else: attribute_split = re.split(r"(\d+)", parameter_attribute) assert len(parameter_attribute) == 2 assert hasattr(original_parameter, attribute_split[0]) parameter_attribute = attribute_split[0] parameter_index = int(attribute_split[1]) - 1 value_list = getattr(original_parameter, parameter_attribute) parameter_value = value_list[parameter_index] if scale_amount is not None: existing_parameter = handler.parameters[parameter_smirks] if np.isclose(parameter_value.value_in_unit(parameter_value.unit), 0.0): # Careful thought needs to be given to this. Consider cases such as # epsilon or sigma where negative values are not allowed. parameter_value = (scale_amount if scale_amount > 0.0 else 0.0) * parameter_value.unit else: parameter_value *= 1.0 + scale_amount if value_list is None: setattr(existing_parameter, parameter_attribute, parameter_value) else: value_list[parameter_index] = parameter_value setattr(existing_parameter, parameter_attribute, value_list) system = force_field.create_openmm_system(topology) if not self.enable_pbc: disable_pbc(system) return system, parameter_value
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.pint.Quantity The temperature to run the simulation at. pressure: simtk.pint.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 evaluator.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