def get_parameter_handler_from_forcefield(self, parameter_handler_name, forcefield): """ It returns a parameter handler from the forcefield based on its name. Parameters ---------- parameter_handler_name : str The name of the parameter handler that is requested forcefield : an openforcefield.typing.engines.smirnoff.ForceField object The forcefield from which the parameter handler will be obtained Returns ------- parameter_handler : an openforcefield.typing.engines.smirnoff.parameters.ParameterHandler object The ParameterHandler that was requested """ from openforcefield.typing.engines.smirnoff import ForceField if isinstance(forcefield, str): forcefield = ForceField(forcefield) elif isinstance(forcefield, ForceField): pass else: raise Exception('Invalid forcefield type') return forcefield.get_parameter_handler(parameter_handler_name)
def test_adding_params_parameterize_flag(): """ Test adding new smirks patterns with cosmetic attributes. """ ff = ForceFieldEditor(forcefield_name="openff-1.0.0.offxml") # add an atom smirks for boron boron = AtomSmirks(smirks="[#5:1]", parameterize={"epsilon"}, atoms={(0,)}, epsilon=0.04, rmin_half=3) # add boron with the flag ff.add_smirks(smirks=[boron, ], parameterize=True) with temp_directory(): ff.forcefield.to_file(filename="boron.offxml") # this should fail if the flag was added with pytest.raises(SMIRNOFFSpecError): _ = ForceField("boron.offxml", allow_cosmetic_attributes=False) boron_ff = ForceField("boron.offxml", allow_cosmetic_attributes=True) # now look for the parameter we added boron_param = boron_ff.get_parameter_handler( "vdW" ).parameters["[#5:1]"] # now make sure it has the param flag param_dict = boron_param.__dict__ assert param_dict["_cosmetic_attribs"] == ["parameterize"] assert param_dict["_parameterize"] == "epsilon"
def test_force_field_custom_handler(mock_entry_point_plugins): """Tests a force field can make use of a custom parameter handler registered through the entrypoint plugin system. """ # Construct a simple FF which only uses the custom handler. force_field_contents = "\n".join([ "<?xml version='1.0' encoding='ASCII'?>", "<SMIRNOFF version='0.3' aromaticity_model='OEAroModel_MDL'>", " <CustomHandler version='0.3'></CustomHandler>", "</SMIRNOFF>" ]) # An exception should be raised when plugins aren't allowed. with pytest.raises(KeyError) as error_info: ForceField(force_field_contents) assert ( "Cannot find a registered parameter handler class for tag 'CustomHandler'" in error_info.value.args[0]) # Otherwise the FF should be created as expected. force_field = ForceField(force_field_contents, load_plugins=True) parameter_handler = force_field.get_parameter_handler("CustomHandler") assert parameter_handler is not None assert parameter_handler.__class__.__name__ == "CustomHandler"
def mock_force_field(parameters: List[SMIRNOFFParameter]): """Mock a ForceBalance forcefield directory.""" # Create the force field directory. force_field = ForceField("openff-1.2.0.offxml") parameters_to_fit = defaultdict(lambda: defaultdict(list)) for parameter in parameters: parameters_to_fit[parameter.handler][parameter.smirks].append( parameter.attribute) for handler_name in parameters_to_fit: handler = force_field.get_parameter_handler(handler_name) for smirks in parameters_to_fit[handler_name]: openff_parameter = handler.parameters[smirks] attributes_string = ", ".join( parameters_to_fit[handler_name][smirks]) openff_parameter.add_cosmetic_attribute("parameterize", attributes_string) os.makedirs("forcefield", exist_ok=True) force_field.to_file(os.path.join("forcefield", "forcefield.offxml"))
def test_update_parameters(smirks_data): """ Test updating the smirks parameters using openff types. """ smirk, result = smirks_data # load a forcefield ff = ForceField("openff-1.0.0.offxml") # all input smirks have parameters set to 0 off_parameter = ff.get_parameter_handler( smirk.type).parameters[smirk.smirks] smirk.update_parameters(off_smirk=off_parameter) for param, value in result.items(): assert getattr(smirk, param) == value
def test_adding_new_smirks_types(smirks): """ Test adding new smirks to a forcefield with and without the parameterize flag. """ param, param_id = smirks ff = ForceFieldEditor("openff-1.0.0.offxml") # now make some new smirks pattern ff.add_smirks(smirks=[param, ], parameterize=True) # now make sure it was added under the correct parameter handler with temp_directory(): ff.forcefield.to_file(filename="bespoke.offxml") new_ff = ForceField("bespoke.offxml", allow_cosmetic_attributes=True) parameter = new_ff.get_parameter_handler(param.type.value).parameters[param.smirks] param_dict = parameter.__dict__ assert param_dict["_cosmetic_attribs"] == ["parameterize"] assert set(param_dict["_parameterize"].split()) == param.parameterize # make sure the id is correct assert param_id in parameter.id
def generateSMIRNOFFStructure(oemol): """ Given an OpenEye molecule (oechem.OEMol), create an OpenMM System and use to generate a ParmEd structure using the SMIRNOFF forcefield parameters. Parameters ---------- oemol : openeye.oechem.OEMol OpenEye molecule Returns ------- molecule_structure : parmed.Structure The resulting Structure """ warnings.warn(DEPRECATION_WARNING_TEXT, PendingDeprecationWarning) from openforcefield.topology import Molecule, Topology from openforcefield.typing.engines.smirnoff import ForceField off_mol = Molecule.from_openeye(oemol) off_top = Topology.from_molecules([off_mol]) mol_ff = ForceField('test_forcefields/smirnoff99Frosst.offxml') # Create OpenMM System and Topology. omm_top = generateTopologyFromOEMol(oemol) # If it's a nonperiodic box, then we can't use default (PME) settings if omm_top.getPeriodicBoxVectors() is None: mol_ff.get_parameter_handler("Electrostatics", {})._method = 'Coulomb' system = mol_ff.create_openmm_system(off_top) # Convert to ParmEd structure. import parmed xyz = extractPositionsFromOEMol(oemol) molecule_structure = parmed.openmm.load_topology(omm_top, system, xyz=xyz) return molecule_structure
def serialise_system(self): """Create the OpenMM system; parametrise using frost; serialise the system.""" # Create an openFF molecule from the rdkit molecule off_molecule = Molecule.from_rdkit(self.molecule.rdkit_mol, allow_undefined_stereo=True) # Make the OpenMM system off_topology = off_molecule.to_topology() forcefield = ForceField("openff_unconstrained-1.0.0.offxml") try: # Parametrise the topology and create an OpenMM System. system = forcefield.create_openmm_system(off_topology) except ( UnassignedValenceParameterException, UnassignedBondParameterException, UnassignedProperTorsionParameterException, UnassignedAngleParameterException, UnassignedMoleculeChargeException, TypeError, ): # If this does not work then we have a molecule that is not in SMIRNOFF so we must add generics # and remove the charge handler to get some basic parameters for the moleucle new_bond = BondHandler.BondType( smirks="[*:1]~[*:2]", length="0 * angstrom", k="0.0 * angstrom**-2 * mole**-1 * kilocalorie", ) new_angle = AngleHandler.AngleType( smirks="[*:1]~[*:2]~[*:3]", angle="0.0 * degree", k="0.0 * mole**-1 * radian**-2 * kilocalorie", ) new_torsion = ProperTorsionHandler.ProperTorsionType( smirks="[*:1]~[*:2]~[*:3]~[*:4]", periodicity1="1", phase1="0.0 * degree", k1="0.0 * mole**-1 * kilocalorie", periodicity2="2", phase2="180.0 * degree", k2="0.0 * mole**-1 * kilocalorie", periodicity3="3", phase3="0.0 * degree", k3="0.0 * mole**-1 * kilocalorie", periodicity4="4", phase4="180.0 * degree", k4="0.0 * mole**-1 * kilocalorie", idivf1="1.0", idivf2="1.0", idivf3="1.0", idivf4="1.0", ) new_vdw = vdWHandler.vdWType( smirks="[*:1]", epsilon=0 * unit.kilocalories_per_mole, sigma=0 * unit.angstroms, ) new_generics = { "Bonds": new_bond, "Angles": new_angle, "ProperTorsions": new_torsion, "vdW": new_vdw, } for key, val in new_generics.items(): forcefield.get_parameter_handler(key).parameters.insert(0, val) # This has to be removed as sqm will fail with unknown elements del forcefield._parameter_handlers["ToolkitAM1BCC"] del forcefield._parameter_handlers["Electrostatics"] # Parametrize the topology and create an OpenMM System. system = forcefield.create_openmm_system(off_topology) # This will tag the molecule so run.py knows that generics have been used. self.fftype = "generics" # Serialise the OpenMM system into the xml file with open("serialised.xml", "w+") as out: out.write(XmlSerializer.serializeSystem(system))
def plot_parameter_changes( original_parameter_path, optimized_parameter_directory, study_names, parameter_smirks, output_directory, ): from simtk import unit as simtk_unit parameter_attributes = ["epsilon", "rmin_half"] default_units = { "epsilon": simtk_unit.kilocalories_per_mole, "rmin_half": simtk_unit.angstrom, } # Find the values of the original and optimized parameters. data_rows = [] for study_name in study_names: original_force_field = ForceField( original_parameter_path, allow_cosmetic_attributes=True, ) optimized_force_field = ForceField( os.path.join(optimized_parameter_directory, f"{study_name}.offxml"), allow_cosmetic_attributes=True, ) original_handler = original_force_field.get_parameter_handler("vdW") optimized_handler = optimized_force_field.get_parameter_handler("vdW") for parameter in original_handler.parameters: if parameter.smirks not in parameter_smirks: continue for attribute_type in parameter_attributes: original_value = getattr(parameter, attribute_type) optimized_value = getattr( optimized_handler.parameters[parameter.smirks], attribute_type) percentage_change = optimized_value - original_value data_row = { "Study": study_name, "Smirks": parameter.smirks, "Attribute": f"{attribute_type} ({default_units[attribute_type]})", "Delta": percentage_change.value_in_unit( default_units[attribute_type]), } data_rows.append(data_row) parameter_data = pandas.DataFrame(data_rows) palette = seaborn.color_palette(n_colors=len(study_names)) plot = seaborn.FacetGrid(parameter_data, row="Attribute", height=4.0, aspect=2.0, sharey=False) plot.map_dataframe(plot_categories, "Smirks", "Delta", "Study", color=palette) plot.add_legend() plot.savefig(os.path.join(output_directory, f"parameter_changes.png"))
class ForceFieldEditor: def __init__(self, forcefield_name: str): """ Gather the forcefield ready for manipulation. Parameters ---------- forcefield_name: str The string of the target forcefield path. Notes ------ This will always try to strip the constraints parameter handler as the FF should be unconstrained for fitting. """ self.forcefield = ForceField(forcefield_name, allow_cosmetic_attributes=True) # try and strip a constraint handler try: del self.forcefield._parameter_handlers["Constraints"] except KeyError: pass def add_smirks( self, smirks: List[Union[AtomSmirks, AngleSmirks, BondSmirks, TorsionSmirks]], parameterize: bool = True, ) -> None: """ Work out which type of smirks this is and add it to the forcefield, if this is not a bespoke parameter update the value in the forcefield. """ _smirks_conversion = { SmirksType.Bonds: BondHandler.BondType, SmirksType.Angles: AngleHandler.AngleType, SmirksType.ProperTorsions: ProperTorsionHandler.ProperTorsionType, SmirksType.Vdw: vdWHandler.vdWType, } _smirks_ids = { SmirksType.Bonds: "b", SmirksType.Angles: "a", SmirksType.ProperTorsions: "t", SmirksType.Vdw: "n", } new_params = {} for smirk in smirks: if smirk.type not in new_params: new_params[smirk.type] = [ smirk, ] else: if smirk not in new_params[smirk.type]: new_params[smirk.type].append(smirk) for smirk_type, parameters in new_params.items(): current_params = self.forcefield.get_parameter_handler( smirk_type).parameters no_params = len(current_params) for i, parameter in enumerate(parameters, start=2): smirk_data = parameter.to_off_smirks() if not parameterize: del smirk_data["parameterize"] # check if the parameter is new try: current_param = current_params[parameter.smirks] smirk_data["id"] = current_param.id # update the parameter using the init to get around conditional assigment current_param.__init__(**smirk_data) except IndexError: smirk_data["id"] = _smirks_ids[smirk_type] + str( no_params + i) current_params.append( _smirks_conversion[smirk_type](**smirk_data)) def label_molecule(self, molecule: off.Molecule) -> Dict[str, str]: """ Type the molecule with the forcefield and return a molecule parameter dictionary. Parameters ---------- molecule: off.Molecule The openforcefield.topology.Molecule that should be labeled by the forcefield. Returns ------- Dict[str, str] A dictionary of each parameter assigned to molecule organised by parameter handler type. """ return self.forcefield.label_molecules(molecule.to_topology())[0] def get_smirks_parameters( self, molecule: off.Molecule, atoms: List[Tuple[int, ...]] ) -> List[Union[AtomSmirks, AngleSmirks, BondSmirks, TorsionSmirks]]: """ For a given molecule label it and get back the smirks patterns and parameters for the requested atoms. """ _atoms_to_params = { 1: SmirksType.Vdw, 2: SmirksType.Bonds, 3: SmirksType.Angles, 4: SmirksType.ProperTorsions, } smirks = [] labels = self.label_molecule(molecule=molecule) for atom_ids in atoms: # work out the parameter type from the length of the tuple smirk_class = _atoms_to_params[len(atom_ids)] # now we can get the handler type using the smirk type off_param = labels[smirk_class.value][atom_ids] smirk = smirks_from_off(off_smirks=off_param) smirk.atoms.add(atom_ids) if smirk not in smirks: smirks.append(smirk) else: # update the covered atoms index = smirks.index(smirk) smirks[index].atoms.add(atom_ids) return smirks def update_smirks_parameters( self, smirks: Iterable[Union[AtomSmirks, AngleSmirks, BondSmirks, TorsionSmirks]], ) -> None: """ Take a list of input smirks parameters and update the values of the parameters using the given forcefield in place. Parameters ---------- smirks : Iterable[Union[AtomSmirks, AngleSmirks, BondSmirks, TorsionSmirks]] An iterable containing smirks schemas that are to be updated. """ for smirk in smirks: new_parameter = self.forcefield.get_parameter_handler( smirk.type).parameters[smirk.smirks] # now we just need to update the smirks with the new values smirk.update_parameters(off_smirk=new_parameter) def get_initial_parameters( self, molecule: off.Molecule, smirks: List[Union[AtomSmirks, AngleSmirks, BondSmirks, TorsionSmirks]], clear_existing: bool = True, ) -> List[Union[AtomSmirks, AngleSmirks, BondSmirks, TorsionSmirks]]: """ Find the initial parameters assigned to the atoms in the given smirks pattern and update the values to match the forcefield. """ labels = self.label_molecule(molecule=molecule) # now find the atoms for smirk in smirks: parameters = labels[smirk.type] if smirk.type == SmirksType.ProperTorsions: # here we can combine multiple parameter types # TODO is this needed? openff_params = [] for atoms in smirk.atoms: param = parameters[atoms] openff_params.append(param) # now check if they are different types types = set([param.id for param in openff_params]) # now update the parameter smirk.update_parameters(off_smirk=openff_params[0], clear_existing=clear_existing) # if there is more than expand the k terms if len(types) > 1: for param in openff_params[1:]: smirk.update_parameters(param, clear_existing=False) else: atoms = list(smirk.atoms)[0] param = parameters[atoms] smirk.update_parameters(off_smirk=param, clear_existing=True) return smirks
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.unit.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 serialise_system(self): """Create the OpenMM system; parametrise using frost; serialise the system.""" # Create an openFF molecule from the rdkit molecule off_molecule = Molecule.from_rdkit(self.molecule.rdkit_mol, allow_undefined_stereo=True) # Make the OpenMM system off_topology = off_molecule.to_topology() # Load the smirnoff99Frosst force field. forcefield = ForceField('test_forcefields/smirnoff99Frosst.offxml') try: # Parametrize the topology and create an OpenMM System. system = forcefield.create_openmm_system(off_topology) except (UnassignedValenceParameterException, TypeError, UnassignedValenceParameterException): # If this does not work then we have a moleucle that is not in SMIRNOFF so we must add generics # and remove the charge handler to get some basic parameters for the moleucle new_bond = BondHandler.BondType( smirks='[*:1]~[*:2]', length="0 * angstrom", k="0.0 * angstrom**-2 * mole**-1 * kilocalorie") new_angle = AngleHandler.AngleType( smirks='[*:1]~[*:2]~[*:3]', angle="0.0 * degree", k="0.0 * mole**-1 * radian**-2 * kilocalorie") new_torsion = ProperTorsionHandler.ProperTorsionType( smirks='[*:1]~[*:2]~[*:3]~[*:4]', periodicity1="1", phase1="0.0 * degree", k1="0.0 * mole**-1 * kilocalorie", periodicity2="2", phase2="180.0 * degree", k2="0.0 * mole**-1 * kilocalorie", periodicity3="3", phase3="0.0 * degree", k3="0.0 * mole**-1 * kilocalorie", periodicity4="4", phase4="180.0 * degree", k4="0.0 * mole**-1 * kilocalorie", idivf1="1.0", idivf2="1.0", idivf3="1.0", idivf4="1.0") new_vdw = vdWHandler.vdWType(smirks='[*:1]', epsilon=0 * unit.kilocalories_per_mole, sigma=0 * unit.angstroms) new_generics = { 'Bonds': new_bond, 'Angles': new_angle, 'ProperTorsions': new_torsion, 'vdW': new_vdw } for key, val in new_generics.items(): forcefield.get_parameter_handler(key).parameters.insert(0, val) # This has to be removed as sqm will fail with unknown elements del forcefield._parameter_handlers['ToolkitAM1BCC'] # Parametrize the topology and create an OpenMM System. system = forcefield.create_openmm_system(off_topology) # This will tag the molecule so run.py knows that generics have been used. self.fftype = 'generics' # Serialise the OpenMM system into the xml file with open('serialised.xml', 'w+') as out: out.write(XmlSerializer.serializeSystem(system))
# Initialize an empty force field from openforcefield.typing.engines.smirnoff import ForceField ff = ForceField() ff._set_aromaticity_model('OEAroModel_MDL') # Loop over the parameters to convert and their corresponding SMIRNOFF header tags for smirnoff_tag, param_dicts in { "Bonds": bond_dicts, "Angles": angle_dicts, "ProperTorsions": proper_dicts, "AmberImproperTorsions": improper_dicts, #'LibraryCharges': charge_dicts, #'vdW': nonbond_dicts }.items(): # Get the parameter handler for the given tag. The # "get_parameter_handler" method will initialize an # empty handler if none exists yet (as is the case here). handler = ff.get_parameter_handler(smirnoff_tag) # Loop over the list of parameter dictionaries, using each one as an input to # handler.add_parameter. This mimics deserializing an OFFXML into a ForceField # object, and will do any sanitization that we might otherwise miss from openforcefield.typing.engines.smirnoff.parameters import DuplicateParameterError for param_dict in param_dicts: try: handler.add_parameter(param_dict) except DuplicateParameterError: continue # Add the ElectrostaticsHandler, with the proper 1-4 scaling factors handler = ff.get_parameter_handler('Electrostatics') # Write the now-populated forcefield out to OFFXML
#!/usr/bin/env python """ Generate a PDF of all small molecules in the JSON dataset. """ import gzip import json import cmiles from openeye import oechem from openforcefield.typing.engines.smirnoff import ForceField forcefield = ForceField('param_valence.offxml', allow_cosmetic_attributes=True) ff_torsion_param_list = forcefield.get_parameter_handler( 'ProperTorsions').parameters def get_torsion_definition(ff_torsion_param_list, tid): for torsion_param in ff_torsion_param_list: if torsion_param.id == tid: answer = torsion_param return answer # Highlight element of interest #subs = oechem.OESubSearch("[#6]") # carbon subs = None # Read the input SMILES def read_molecules(input_json): """
def find_smirks_parameters(parameter_tag='vdW', *smiles_patterns): """Finds those force field parameters with a given tag which would be assigned to a specified set of molecules defined by the their smiles patterns. Parameters ---------- parameter_tag: str The tag of the force field parameters to find. smiles_patterns: str The smiles patterns to assign the force field parameters to. Returns ------- dict of str and list of str A dictionary with keys of parameter smirks patterns, and values of lists of smiles patterns which would utilize those parameters. """ stdout_ = sys.stdout # Keep track of the previous value. stderr_ = sys.stderr # Keep track of the previous value. stream = StringIO() sys.stdout = stream sys.stderr = stream force_field = ForceField('smirnoff99Frosst-1.1.0.offxml') sys.stdout = stdout_ # restore the previous stdout. sys.stderr = stderr_ parameter_handler = force_field.get_parameter_handler(parameter_tag) smiles_by_parameter_smirks = {} # Initialize the array with all possible smirks pattern # to make it easier to identify which are missing. for parameter in parameter_handler.parameters: if parameter.smirks in smiles_by_parameter_smirks: continue smiles_by_parameter_smirks[parameter.smirks] = set() # Populate the dictionary using the open force field toolkit. for smiles in smiles_patterns: if smiles not in cached_smirks_parameters or parameter_tag not in cached_smirks_parameters[smiles]: try: molecule = Molecule.from_smiles(smiles) except UndefinedStereochemistryError: # Skip molecules with undefined stereochemistry. continue topology = Topology.from_molecules([molecule]) if smiles not in cached_smirks_parameters: cached_smirks_parameters[smiles] = {} if parameter_tag not in cached_smirks_parameters[smiles]: cached_smirks_parameters[smiles][parameter_tag] = [] cached_smirks_parameters[smiles][parameter_tag] = [ parameter.smirks for parameter in force_field.label_molecules(topology)[0][parameter_tag].values() ] parameters_with_tag = cached_smirks_parameters[smiles][parameter_tag] for smirks in parameters_with_tag: smiles_by_parameter_smirks[smirks].add(smiles) return smiles_by_parameter_smirks