from openff.toolkit.typing.engines.smirnoff import ForceField from amberimpropertorsionhandler import AmberImproperTorsionHandler ff = ForceField('result_residues.offxml', 'result_backbone.offxml') ff.to_file('result_merged.offxml')
from openff.toolkit.typing.engines.smirnoff import ForceField ff = ForceField() ff.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 openff.toolkit.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 ff.to_file('result_residues.offxml')
ff.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 openff.toolkit.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 ff.to_file('result_backbone.offxml')
def _combine_molecules_offxml( molecules: List["Ligand"], parameters: List[str], rfree_data: Dict[str, Dict[str, Union[str, float]]], filename: str, water_model: Literal["tip3p"] = "tip3p", ): """ Main worker function to build the combined offxmls. """ if sum([molecule.extra_sites.n_sites for molecule in molecules]) > 0: raise NotImplementedError( "Virtual sites can not be safely converted into offxml format yet." ) if sum([molecule.RBTorsionForce.n_parameters for molecule in molecules]) > 0: raise NotImplementedError( "RBTorsions can not yet be safely converted into offxml format yet." ) try: from chemper.graphs.cluster_graph import ClusterGraph except ModuleNotFoundError: raise ModuleNotFoundError( "chemper is required to make an offxml, please install with `conda install chemper -c conda-forge`." ) fit_ab = False # if alpha and beta should be fit if "AB" in parameters: fit_ab = True rfree_codes = set() # keep track of all rfree codes used by these molecules # create the master ff offxml = ForceField(allow_cosmetic_attributes=True, load_plugins=True) offxml.author = f"QUBEKit_version_{qubekit.__version__}" offxml.date = datetime.now().strftime("%Y_%m_%d") # get all of the handlers _ = offxml.get_parameter_handler("Constraints") bond_handler = offxml.get_parameter_handler("Bonds") angle_handler = offxml.get_parameter_handler("Angles") proper_torsions = offxml.get_parameter_handler("ProperTorsions") improper_torsions = offxml.get_parameter_handler("ImproperTorsions") _ = offxml.get_parameter_handler( "Electrostatics", handler_kwargs={"scale14": 0.8333333333, "version": 0.3} ) using_plugin = False if parameters: # if we want to optimise the Rfree we need our custom handler vdw_handler = offxml.get_parameter_handler( "QUBEKitvdWTS", allow_cosmetic_attributes=True ) using_plugin = True else: vdw_handler = offxml.get_parameter_handler( "vdW", allow_cosmetic_attributes=True ) library_charges = offxml.get_parameter_handler("LibraryCharges") for molecule in molecules: rdkit_mol = molecule.to_rdkit() bond_types = molecule.bond_types # for each bond type collection create a single smirks pattern for bonds in bond_types.values(): graph = ClusterGraph( mols=[rdkit_mol], smirks_atoms_lists=[bonds], layers="all" ) qube_bond = molecule.BondForce[bonds[0]] bond_handler.add_parameter( parameter_kwargs={ "smirks": graph.as_smirks(), "length": qube_bond.length * unit.nanometers, "k": qube_bond.k * unit.kilojoule_per_mole / unit.nanometers**2, } ) angle_types = molecule.angle_types for angles in angle_types.values(): graph = ClusterGraph( mols=[rdkit_mol], smirks_atoms_lists=[angles], layers="all", ) qube_angle = molecule.AngleForce[angles[0]] angle_handler.add_parameter( parameter_kwargs={ "smirks": graph.as_smirks(), "angle": qube_angle.angle * unit.radian, "k": qube_angle.k * unit.kilojoule_per_mole / unit.radians**2, } ) torsion_types = molecule.dihedral_types for dihedrals in torsion_types.values(): graph = ClusterGraph( mols=[rdkit_mol], smirks_atoms_lists=[dihedrals], layers="all", ) qube_dihedral = molecule.TorsionForce[dihedrals[0]] proper_torsions.add_parameter( parameter_kwargs={ "smirks": graph.as_smirks(), "k1": qube_dihedral.k1 * unit.kilojoule_per_mole, "k2": qube_dihedral.k2 * unit.kilojoule_per_mole, "k3": qube_dihedral.k3 * unit.kilojoule_per_mole, "k4": qube_dihedral.k4 * unit.kilojoule_per_mole, "periodicity1": qube_dihedral.periodicity1, "periodicity2": qube_dihedral.periodicity2, "periodicity3": qube_dihedral.periodicity3, "periodicity4": qube_dihedral.periodicity4, "phase1": qube_dihedral.phase1 * unit.radians, "phase2": qube_dihedral.phase2 * unit.radians, "phase3": qube_dihedral.phase3 * unit.radians, "phase4": qube_dihedral.phase4 * unit.radians, "idivf1": 1, "idivf2": 1, "idivf3": 1, "idivf4": 1, } ) improper_types = molecule.improper_types for torsions in improper_types.values(): impropers = [ (improper[1], improper[0], *improper[2:]) for improper in torsions ] graph = ClusterGraph( mols=[rdkit_mol], smirks_atoms_lists=[impropers], layers="all" ) qube_improper = molecule.ImproperTorsionForce[torsions[0]] # we need to multiply each k value by as they will be applied as trefoil see # <https://openforcefield.github.io/standards/standards/smirnoff/#impropertorsions> for more details # we assume we only have a k2 term for improper torsions via a periodic term improper_torsions.add_parameter( parameter_kwargs={ "smirks": graph.as_smirks(), "k1": qube_improper.k2 * 3 * unit.kilojoule_per_mole, "periodicity1": qube_improper.periodicity2, "phase1": qube_improper.phase2 * unit.radians, } ) atom_types = {} for atom_index, cip_type in molecule.atom_types.items(): atom_types.setdefault(cip_type, []).append((atom_index,)) for sym_set in atom_types.values(): graph = ClusterGraph( mols=[rdkit_mol], smirks_atoms_lists=[sym_set], layers="all" ) qube_non_bond = molecule.NonbondedForce[sym_set[0]] rfree_code = _get_parameter_code( molecule=molecule, atom_index=sym_set[0][0] ) atom_data = { "smirks": graph.as_smirks(), } if rfree_code in parameters or fit_ab: # keep track of present codes to optimise rfree_codes.add(rfree_code) if using_plugin: # this is to be refit atom = molecule.atoms[qube_non_bond.atoms[0]] atom_data["volume"] = atom.aim.volume * unit.angstroms**3 else: atom_data["epsilon"] = qube_non_bond.epsilon * unit.kilojoule_per_mole atom_data["sigma"] = qube_non_bond.sigma * unit.nanometers vdw_handler.add_parameter(parameter_kwargs=atom_data) charge_data = dict( (f"charge{param.atoms[0] + 1}", param.charge * unit.elementary_charge) for param in molecule.NonbondedForce ) charge_data["smirks"] = molecule.to_smiles(mapped=True) library_charges.add_parameter(parameter_kwargs=charge_data) # now loop over all the parameters to be fit and add them as cosmetic attributes to_parameterize = [] for parameter_to_fit in parameters: if parameter_to_fit != "AB" and parameter_to_fit in rfree_codes: setattr( vdw_handler, f"{parameter_to_fit.lower()}free", unit.Quantity( rfree_data[parameter_to_fit]["r_free"], unit=unit.angstroms ), ) to_parameterize.append(f"{parameter_to_fit.lower()}free") if fit_ab: vdw_handler.alpha = rfree_data["alpha"] vdw_handler.beta = rfree_data["beta"] to_parameterize.extend(["alpha", "beta"]) if to_parameterize: vdw_handler.add_cosmetic_attribute("parameterize", ", ".join(to_parameterize)) # now add a water model to the force field _add_water_model( force_field=offxml, water_model=water_model, using_plugin=using_plugin ) offxml.to_file(filename=filename)